sdk

module
v4.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT

README

The Drift SDK

The SDK is what user-written code imports to talk to Drift. It exists in six languages — Go, Python, Node.js, Ruby, PHP, and Rust — and every one exposes the same API surface, follows the same wire protocol, and provides the same local-development experience. This repository (github.com/ondrift/sdk) is the single authoritative source for all six; one tag per release versions them together.

Contents


Install

No version is pinned anywhere — reference the SDK unversioned and every build tracks the latest tag.

# Go — name the ROOT module (NOT …/sdk/v4/go: the repo's early history had a
# nested …/sdk/go module whose stale pseudo-versions still resolve first).
# The /v4 segment is Go's own semantic-import-versioning rule for a v2+
# module — a plain `github.com/ondrift/sdk@latest` (no /v4) stays on the v1
# line forever, by design: existing v1 consumers never see a breaking change
# land under their feet.
go get github.com/ondrift/sdk/v4@latest      # then: import drift "github.com/ondrift/sdk/v4/go"
# Python (requirements.txt)
drift-sdk @ git+https://github.com/ondrift/sdk.git#subdirectory=python
# Node.js (package.json dependency) — #semver:* = latest tag
npm i "github:ondrift/sdk#semver:*"
# Ruby (Gemfile) — branch:master is the repo default (and bundler's default
# for git gems); glob locates the gemspec in ruby/
gem "drift-sdk", git: "https://github.com/ondrift/sdk", branch: "master", glob: "ruby/*.gemspec"
# PHP — VCS repo + the "*" constraint tracks the latest tag
composer config repositories.drift vcs https://github.com/ondrift/sdk
composer require "ondrift/sdk:*"
# Rust (Cargo.toml)
drift-sdk = { git = "https://github.com/ondrift/sdk" }

Writing a function

A Drift Atomic function is a named handler with an @atomic annotation. The CLI reads the annotation and generates the program's entry point — you don't write main() or call run() yourself (Ruby is the one exception; see below).

  • Annotation@atomic http=<method>:<route> auth=<none|jwt|…> for an HTTP route, or @atomic queue=<name> for a queue consumer. Path params use :name (e.g. reviewer/decision/:id).
  • Arguments(req) for a request with no body (GET); (body, req) when there's a request body or queue payload.
  • Return(status, message, payload), with an optional 4th headers map. It's a tuple in Go/Python/Rust, an array in Node/PHP, a hash in Ruby.
  • Request — read path params from req.params, query from req.query, headers from req.headers, raw body from req.body.
  • Streaming — annotate stream=sse or stream=ws; the handler receives an extra emit (SSE) or conn (WebSocket) argument.

Everything stateful lives under the Backbone namespace — the B of the sacred A·B·C triad and the sole entrypoint for state. Nothing stateful sits at the top level. Backbone groups secrets, key/value cache, NoSQL documents, queues, blobs, locks, relational sql(name), and realtime channels — all shown per-language below. Identity lives under a separate Deed namespace instead — a peer pillar, not a Backbone primitive, with its own listener/port (DEED_URL) separate from Backbone's — grouping passwordless KeyAuth (Ed25519 device-key auth), general-purpose JWT sign/verify, the zero-knowledge Vault (recovery store), Link (multi-device attestation/enrollment/revocation), and Pocket (E2EE per-identity app data). Cross-slice calling (slice(name) / callerSlice(req)) stays at the top level on purpose: it's inter-slice networking, a different, still-hypothetical future pillar — not Backbone, and not the same thing as Deed's Link (which enrolls a device for one identity, not a slice-to-slice call).


API Reference

The Backbone and Deed primitives are each identical across languages; every section shows that language's idiomatic form. Reach state through drift.Backbone (Go), drift.backbone (Python/Node), Drift::Backbone (Ruby), \Drift\Backbone\… (PHP), drift_sdk::backbone (Rust); reach identity through the parallel Deed / deed / Drift::Deed / \Drift\Deed\… / drift_sdk::deed namespace — never the top level.

Go
import drift "github.com/ondrift/sdk/v4/go"
// @atomic http=post:reviewer/decision/:id auth=none
func PostReviewerDecisionId(body RequestBody, req drift.Request) (int, string, any, map[string]string) {
    id := strings.TrimSpace(req.Params["id"])
    if id == "" {
        return 400, "Bad Request", map[string]string{"error": "id required"}, nil
    }
    return 200, "OK", map[string]any{"id": id, "action": body.Action}, nil
}
// GET handlers take just (req drift.Request).

Backbone — the sole entrypoint for state (drift.Backbone.*)

drift.Backbone.Secret.Get("API_KEY")                           // .Set(k, v), .Delete(k)
c := drift.Backbone.NoSQL.Collection("orders")
c.Insert(doc); c.Get(id); c.Read(key); c.List(filter); c.Delete(key); c.Drop()
drift.Backbone.Cache.Set("k", v, 60); drift.Backbone.Cache.Get("k")        // ttl seconds
drift.Backbone.Queue("q").Push(m); drift.Backbone.Queue("q").Pop()
drift.Backbone.Blob.Put("k", data, "image/png"); drift.Backbone.Blob.Get("k")
drift.Backbone.Lock.Acquire("r", 30); drift.Backbone.Lock.Release("r", token)
drift.Backbone.SQL("clinic").Query("SELECT …", args)           // .Execute(…), .Begin() (transactions)
drift.Backbone.Realtime.Channel("events").Publish(msg)         // fan-out → WS subscribers at /realtime/events; .Presence()

Deed — identity, a peer pillar, not Backbone (drift.Deed.*)

drift.Deed.KeyAuth.Challenge(pubkey); drift.Deed.KeyAuth.Verify(pubkey, sig, "my-app")  // passwordless Ed25519 → this slice's JWT
drift.Deed.JWT.Issue(drift.JWTClaims{Sub: "alice"}); drift.Deed.JWT.Verify(tok, drift.JWTVerifyOptions{})
drift.Deed.Vault.Put(uid, blob); drift.Deed.Vault.Get(uid)                 // zero-knowledge recovery store, dedicated routes
drift.Deed.Link.Begin(pubkey); drift.Deed.Link.Attest(identity, sid, attestingPubkey, sig)
drift.Deed.Link.Complete(sid); drift.Deed.Link.Revoke(identity, targetPubkey, revokingPubkey, sig)
drift.Deed.Pocket.Set(token, "k", blob); drift.Deed.Pocket.Get(token, "k")  // every call takes the bearer token explicitly
// Top-level (NOT Backbone, NOT Deed) — cross-slice networking + utilities:
drift.Slice("c12").Post("/api/events", body); drift.Slice("c12").Get("/x")  // call a LINKED slice (`drift slice link add c12`); carries X-Drift-Slice
drift.CallerSlice(req)                                          // name of the linked slice that called you ("" if not via a link)
drift.Log("msg"); drift.HTTPRequest("GET", url, nil, nil)      // HTTPRequestWithTimeout to override 30s
Python
import drift   # requires Python 3.9+
# @atomic http=post:submit auth=none
def post_submit(body, req):
    if not body.get("permit_type"):
        return 400, "Bad Request", {"error": "permit_type is required"}
    return 200, "OK", {"ok": True}
# GET handlers take just (req).

Backbone — the sole entrypoint for state (drift.backbone.*)

drift.backbone.secret.get("API_KEY")                          # .set(k, v), .delete(k)
c = drift.backbone.nosql.collection("orders")
c.insert(doc); c.get(id); c.read(key); c.list(filter); c.delete(key); c.drop()
drift.backbone.cache.set("k", v, 60); drift.backbone.cache.get("k"); drift.backbone.cache.delete("k")
drift.backbone.queue("q").push(m); drift.backbone.queue("q").pop()
drift.backbone.blob.put("k", data); drift.backbone.blob.get("k")
drift.backbone.lock.acquire("r", ttl=30); drift.backbone.lock.release("r", token)
db = drift.backbone.sql("clinic"); db.query("SELECT …", args)  # .execute(…), .begin()
drift.backbone.realtime.channel("events").publish(msg)        # fan-out → WS subscribers at /realtime/events; .presence()

Deed — identity, a peer pillar, not Backbone (drift.deed.*)

drift.deed.keyauth.challenge(pubkey); drift.deed.keyauth.verify(pubkey, sig, "my-app")
drift.deed.jwt.issue({"sub": "alice"}); drift.deed.jwt.verify(tok)              # raises drift.JWTError
drift.deed.vault.put(uid, blob); drift.deed.vault.get(uid)                     # zero-knowledge recovery store, dedicated routes
drift.deed.link.begin(pubkey); drift.deed.link.attest(identity, sid, attesting_pubkey, sig)
drift.deed.link.complete(sid); drift.deed.link.revoke(identity, target_pubkey, revoking_pubkey, sig)
drift.deed.pocket.set(token, "k", blob); drift.deed.pocket.get(token, "k")     # every call takes the bearer token explicitly
# Top-level (NOT Backbone, NOT Deed) — cross-slice networking + utilities:
drift.slice("c12").post("/api/events", body); drift.slice("c12").get("/x")
drift.caller_slice(req)
drift.log("msg"); drift.http_request("GET", url, timeout=30)
Node.js
const drift = require("@ondrift/sdk");   // requires Node.js 18+
// @atomic http=get:status/:token auth=none
async function getStatusToken(req) {
  const token = (req.params && req.params.token) || "";
  if (!token) return [400, "Bad Request", { error: "token required" }];
  return [200, "OK", { token }];
}
module.exports = { getStatusToken };   // export the handler for the CLI wrapper

Backbone — the sole entrypoint for state (drift.backbone.*; calls are asyncawait them)

await drift.backbone.secret.get("API_KEY");                   // .set(k, v), .delete(k)
const c = drift.backbone.nosql.collection("orders");
await c.insert(doc); await c.get(id); await c.read(key); await c.list(filter); await c.delete(key); await c.drop();
await drift.backbone.cache.set("k", v, 60); await drift.backbone.cache.get("k"); await drift.backbone.cache.delete("k");
await drift.backbone.queue("q").push(m); await drift.backbone.queue("q").pop();
await drift.backbone.blob.put("k", data); await drift.backbone.blob.get("k");
await drift.backbone.lock.acquire("r", 30); await drift.backbone.lock.release("r", token);
const db = drift.backbone.sql("clinic"); await db.query("SELECT …", args);   // .execute(…), .begin()
await drift.backbone.realtime.channel("events").publish(msg); // fan-out → WS subscribers at /realtime/events; .presence()

Deed — identity, a peer pillar, not Backbone (drift.deed.*; calls are asyncawait them)

await drift.deed.keyauth.challenge(pubkey); await drift.deed.keyauth.verify(pubkey, sig, "my-app");
await drift.deed.jwt.issue({ sub: "alice" }); await drift.deed.jwt.verify(tok);   // throws drift.JWTError
await drift.deed.vault.put(uid, blob); await drift.deed.vault.get(uid);          // zero-knowledge recovery store, dedicated routes
await drift.deed.link.begin(pubkey); await drift.deed.link.attest(identity, sid, attestingPubkey, sig);
await drift.deed.link.complete(sid); await drift.deed.link.revoke(identity, targetPubkey, revokingPubkey, sig);
await drift.deed.pocket.set(token, "k", blob); await drift.deed.pocket.get(token, "k");  // every call takes the bearer token explicitly
// Top-level (NOT Backbone, NOT Deed) — cross-slice networking + utilities:
await drift.slice("c12").post("/api/events", body); await drift.slice("c12").get("/x");
drift.callerSlice(req);
drift.log("msg"); await drift.httpRequest("GET", url);                  // { timeoutMs } 5th arg
Ruby
require "drift"   # requires Ruby 3.0+
# @atomic http=get:reviewer/queue auth=none
def get_reviewer_queue(req)
  rows = Drift::Backbone::Nosql.collection("submissions").list
  { "status" => 200, "message" => "OK", "payload" => { "count" => rows.length } }
end

Drift.run(method(:get_reviewer_queue))   # Ruby: end the file with this

Backbone — the sole entrypoint for state (Drift::Backbone::*)

Drift::Backbone::Secret.get("API_KEY")                        # .set(k, v), .delete(k)
c = Drift::Backbone::Nosql.collection("orders")
c.insert(doc); c.get(id); c.list(filter)
Drift::Backbone::Cache.get("k"); Drift::Backbone::Cache.set("k", v, ttl: 60)
Drift::Backbone.queue("q").push(m); Drift::Backbone.queue("q").pop
Drift::Backbone::Blob.put("k", data, content_type: "image/png"); Drift::Backbone::Blob.get("k")
Drift::Backbone::Lock.acquire("r", ttl: 30); Drift::Backbone::Lock.release("r", token)
db = Drift::Backbone.sql("clinic"); db.query("SELECT …", args)   # .execute(…), .transaction { |tx| … }
Drift::Backbone::Realtime.channel("events").publish(msg)      # fan-out → WS subscribers at /realtime/events; .presence

Deed — identity, a peer pillar, not Backbone (Drift::Deed::*)

Drift::Deed::KeyAuth.challenge(pubkey); Drift::Deed::KeyAuth.verify(pubkey, sig, "my-app")
Drift::Deed::JWT.issue(sub: "alice", exp: Time.now.to_i + 3600); Drift::Deed::JWT.verify(tok)   # raises Drift::DeedError
Drift::Deed::Vault.put(uid, blob); Drift::Deed::Vault.get(uid)                 # zero-knowledge recovery store, dedicated routes
Drift::Deed::Link.begin(pubkey); Drift::Deed::Link.attest(identity, sid, attesting_pubkey, sig)
Drift::Deed::Link.complete(sid); Drift::Deed::Link.revoke(identity, target_pubkey, revoking_pubkey, sig)
Drift::Deed::Pocket.set(token, "k", blob); Drift::Deed::Pocket.get(token, "k")  # every call takes the bearer token explicitly
# Top-level (NOT Backbone, NOT Deed) — cross-slice networking + utilities:
Drift.slice("c12").post("/api/events", body); Drift.slice("c12").get("/x")
Drift.caller_slice(req)
Drift.log("msg"); Drift.http_request("GET", url, timeout: 30)

The local-dev server uses webrick (lazy-required; Ruby 3.0+ dropped it from stdlib). Add gem "webrick" to your Gemfile if needed.

PHP
// installed via Composer → \Drift\… is autoloaded (requires PHP 8.1+)
<?php
// @atomic queue=notify auth=none
function queue_notifier($body, $req = null) {
    $id = is_array($body) ? ($body['submission_id'] ?? '') : '';
    if ($id === '') return [200, 'OK', ['ok' => false]];
    return [200, 'OK', ['ok' => true]];
}
// HTTP handlers take ($req) for GET, ($body, $req) when there's a body.

Backbone — the sole entrypoint for state (\Drift\Backbone\…)

\Drift\Backbone\Secret::get('API_KEY');                       // ::set($k, $v), ::delete($k)
$c = \Drift\Backbone\Nosql::collection('orders');
$c->insert($doc); $c->get($id); $c->read($key); $c->list($filter); $c->delete($key); $c->drop();
\Drift\Backbone\Cache::set('k', $v, 60); \Drift\Backbone\Cache::get('k'); \Drift\Backbone\Cache::delete('k');
\Drift\Backbone\queue('q')->push($m); \Drift\Backbone\queue('q')->pop();
\Drift\Backbone\Blob::put('k', $data); \Drift\Backbone\Blob::get('k');
\Drift\Backbone\Lock::acquire('r', 30); \Drift\Backbone\Lock::release('r', $token);
$db = \Drift\Backbone\sql('clinic'); $db->query('SELECT …', $args);   // ->execute(…), ->transaction(fn($tx)=>…)
\Drift\Backbone\Realtime::channel('events')->publish($msg);   // fan-out → WS subscribers at /realtime/events; ->presence()

Deed — identity, a peer pillar, not Backbone (\Drift\Deed\…)

\Drift\Deed\KeyAuth::challenge($pubkey); \Drift\Deed\KeyAuth::verify($pubkey, $sig, 'my-app');
\Drift\Deed\JWT::issue(['sub' => 'alice']); \Drift\Deed\JWT::verify($tok);     // throws \Drift\JWTError
\Drift\Deed\Vault::put($uid, $blob); \Drift\Deed\Vault::get($uid);            // zero-knowledge recovery store, dedicated routes
\Drift\Deed\Link::begin($pubkey); \Drift\Deed\Link::attest($identity, $sid, $attestingPubkey, $sig);
\Drift\Deed\Link::complete($sid); \Drift\Deed\Link::revoke($identity, $targetPubkey, $revokingPubkey, $sig);
\Drift\Deed\Pocket::set($token, 'k', $blob); \Drift\Deed\Pocket::get($token, 'k');   // list_keys($token) — `list` is a PHP language construct
// Top-level (NOT Backbone, NOT Deed) — cross-slice networking + utilities:
\Drift\slice('c12')->post('/api/events', $body); \Drift\slice('c12')->get('/x');
\Drift\caller_slice($req);
\Drift\log('msg'); \Drift\http_request('GET', $url);
Rust
[dependencies]
drift-sdk  = { git = "https://github.com/ondrift/sdk" }
serde_json = "1"
use drift_sdk::{self as drift, Value};
use serde_json::json;

// @atomic queue=validate auth=none
pub fn queue_validator(body: Value, _req: Value) -> (i64, &'static str, Value) {
    let id = body.get("submission_id").and_then(|v| v.as_str()).unwrap_or("");
    if id.is_empty() {
        return (200, "OK", json!({ "ok": false }));
    }
    (200, "OK", json!({ "ok": true }))
}

Backbone — the sole entrypoint for state (drift_sdk::backbone::*)

use drift_sdk::backbone::{secret, cache, nosql, queue, blob, lock, sql, realtime};
secret::get("API_KEY");                                       // set(k, v), delete(k)
let c = nosql::collection("orders");
c.insert(doc); c.get(id); c.read(key); c.list(Some(filter)); c.delete(key); c.drop();
cache::set("k", v, 60); cache::get("k"); cache::delete("k");
queue("q").push(m); queue("q").pop();
blob::put("k", &data, Some("image/png")); blob::get("k");
lock::acquire("r", 30); lock::release("r", &token);
let db = sql("clinic"); db.query("SELECT …", &args);         // .execute(…), .begin()
realtime::channel("events").publish(msg);                    // fan-out → WS subscribers at /realtime/events; .presence()

Deed — identity, a peer pillar, not Backbone (drift_sdk::deed::*)

use drift_sdk::deed::{keyauth, jwt, vault, link, pocket};
keyauth::challenge(&pubkey); keyauth::verify(&pubkey, &sig, "my-app");
jwt::issue(claims); jwt::verify(&tok, None, None);                     // -> Result<Value, jwt::JWTError>
vault::put(uid, blob); vault::get(uid);                                // zero-knowledge recovery store, dedicated routes -> Result<_, deed::DeedError>
link::begin(&pubkey); link::attest(&identity, &sid, &attesting_pubkey, &sig);
link::complete(&sid); link::revoke(&identity, &target_pubkey, &revoking_pubkey, &sig);
pocket::set(&token, "k", blob); pocket::get(&token, "k");              // every call takes the bearer token explicitly
// Top-level (NOT Backbone, NOT Deed) — cross-slice networking + utilities:
drift_sdk::slice("c12").post("/api/events", Some(body)); drift_sdk::slice("c12").get("/x");
drift_sdk::caller_slice(&req);
drift_sdk::log("msg"); drift_sdk::http_request("GET", url, None, None);

ureq v2 is the maintained line we use today; ureq v3 (2025) isn't a drop-in upgrade — re-evaluate when its API stabilises.

Outbound HTTPS is opt-in. By default the SDK is pure Rust (http_request does plain HTTP), so a Rust function cross-compiles to the runner with just rustup — no C toolchain. To call https:// URLs, enable the tls feature:

drift-sdk = { git = "https://github.com/ondrift/sdk", features = ["tls"] }

That pulls ring (C/assembly), so deploying then needs a C cross-toolchain — install zig and cargo install cargo-zigbuild, or a musl cross-gcc. Calling https:// without the feature returns a clear error rather than failing silently.


License

MIT — see LICENSE.


This is the single source of truth for all six SDKs. Any change to the public API surface of any language MUST update the matching section above in the same change. Last verified 2026-07-15 — all six languages namespaced under Backbone (sole entrypoint for state: Secret/NoSQL/Cache/Queue/Blob/Lock/SQL/Realtime) and the parallel Deed (identity: KeyAuth/JWT/Vault/Link/Pocket — a peer pillar, not a Backbone primitive), plus top-level slice-link.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL