techne-sdk 0.1.0

TechneSDK's logic written once: version ranking (eventState) and offline record verification over the published check, shipped to TypeScript as WebAssembly; with opt-in atproto plumbing (service auth, DID resolution, signed labels, proven record fetches, PDS password sessions) behind the `crypto` and `net` features.
Documentation

techne-sdk

TechneSDK for apps on the Techne platform: sign in, put, query, useEvent and the read-time check.

  • npm: one package, techne-sdk, with subpaths.
  • Rust: the crate techne-sdk (crates/techne-sdk). It holds the logic that must give the platform's answers exactly: the check, the version ranking (eventState) and put's private-first, public-last plan. The npm package runs the same crate as WebAssembly, or as wasm2js on hosts without WebAssembly (Hermes). Opt-in features add the atproto plumbing services share (no platform crate either): crypto for service-auth JWTs, signed labels, record proofs and service-account passwords, and net for DID resolution, verified record fetches and PDS password sessions.

The SDK depends on two things only: the published lexicons (the lexicons repo at a tag) and the platform's published API. It contains no platform code.

Install

npm i techne-sdk          # react >= 19 is an optional peer, needed only for techne-sdk/react

Every import goes through the package's exports, so the resolver has to read them:

  • TypeScript: moduleResolution node16, nodenext or bundler. Under the old node (node10) mode the subpath types do not resolve.
  • React Native: Metro with package exports on (unstable_enablePackageExports, the default from Expo SDK 53). techne-sdk/wasm loads the check through techne-sdk/wasm/load, whose react-native condition picks the wasm2js build (Hermes has no WebAssembly); that file is ESM that calls Metro's require.
Import What it holds
techne-sdk (also techne-sdk/generated) the generated XRPC client (AtpBaseClient) and every lexicon type
techne-sdk/lexicons the generated lexicon dictionary (schemaDict, schemas, lexicons, ids): every lexicon document the client was built from, for reading descriptions at run time (large; keep it server side)
techne-sdk/types/<nsid path> one lexicon's types, e.g. techne-sdk/types/social/scenius/getContactInvite
techne-sdk/react useEvent(uri): one commons event or place as its versions, with the check's verdict
techne-sdk/versions the same state without React: eventState, verifyAnswer, version labels
techne-sdk/connect connect(session, platform) / connectHosted(did, fetchHandler): the clients over one session
techne-sdk/put put(techne, records, { deletes }): the one write, deletes included
techne-sdk/query the platform's generic queries: listRecords, getRecords, hosts
techne-sdk/links link tokens: create, redeem, check, revoke
techne-sdk/wasm the check itself: validate, decide, verify
techne-sdk/catalog the browsable lexicon catalog
techne-sdk/nsid, techne-sdk/nsid.json every NSID as a constant (NSID, METHOD)

Datetime fields are plain strings: send Date.prototype.toISOString() (UTC, trailing Z).

Reading one event (the platform's getVersions is public, so an anonymous client will do):

import { AtpBaseClient } from 'techne-sdk'
import { useEvent } from 'techne-sdk/react'

const platform = new AtpBaseClient({ service: 'https://<platform host>' })
const load = async (uri: string) => (await platform.pub.techne.draft.commons.getVersions({ uri })).data

function Event({ uri }: { uri: string }) {
  const { loading, error, fields } = useEvent(uri, { load })
  if (loading) return <p>Loading</p>
  if (error) return <p>Could not load it</p>
  return <h1>{String(fields?.name?.value ?? '')}</h1>
}

Pass { claims } too, and verification holds the check's answer for every record (valid, signer, chain verified).

Layout

Path What
src/ the TypeScript package. src/generated/, src/catalog/catalog.gen.ts and src/wasm/*.gen.* are generated and committed
crates/techne-sdk/ the Rust crate, with the shared fixtures (fixtures/cases.json) both languages must answer identically
inputs/ the codegen inputs, copied by scripts/inputs-sync.mjs and pinned in inputs.pin.json: inputs/lexicons/ (the lexicons repo at lexicons.tag) and inputs/platform-api/ (the platform's routed method lexicons, the atproto methods it serves and its NSID rename map, at platformApi.rev)
lexicons-base/ standard atproto repo-op lexicons the generated client needs
dev-pds/ a real atproto PDS and PLC in one Node process, for end-to-end runs against a platform
scripts/ codegen (gen.sh), the WebAssembly build (build-wasm.sh), the package build (build.mjs), the gate

Changing it

  • A lexicon changed: tag the lexicons repo. Set lexicons.tag and lexicons.commit in inputs.pin.json and the techne-lexicon-check tag in Cargo.toml, then run cargo update -p techne-lexicon-check and node scripts/inputs-sync.mjs --lexicons <lexicons checkout> --platform <platform checkout>. Then run npm run codegen and commit.
  • The platform's API changed: set platformApi.rev (a platform tag for releases), then run the same sync and codegen.
  • The Rust logic changed: edit crates/techne-sdk and run npm run codegen (it rebuilds the WebAssembly). The fixtures must still agree.

npm run codegen needs the pinned Rust toolchain (rust-toolchain.toml) with the wasm32-unknown-unknown target, and npx for binaryen. The build is reproducible, so the gate can diff it against the committed one.

The gate

bash scripts/gate.sh     # inputs, codegen (no drift), typecheck, test, package, rust

package builds dist/, then loads every subpath through exports in plain Node and runs the fixtures through the built WebAssembly and wasm2js modules. It also checks the npm pack file list with a dry run. rust runs the same fixtures through the crate.

Releasing

See RELEASING.md.