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

```sh
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):

```tsx
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

```sh
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](RELEASING.md).