pnpm + Turborepo monorepo of TypeScript SDKs under packages/: a framework-agnostic core
(@thunderid/javascript), a browser layer (@thunderid/browser), a server layer (@thunderid/node), and
framework SDKs built on top of those (react, vue, nuxt, nextjs, express, react-router,
tanstack-router). samples/<framework>/quickstart directories contain standalone demo apps — not part of
any published package.
Every change to this SDK goes through the ThunderID SDK development specification. It is maintained in the product repository and governs all four SDK repositories, so it is linked here rather than copied.
- SDK development specification: the contract this SDK implements. Layering, operational modes, configuration keys, client surface, error model, security floor, platform packaging, testing, and the cross-SDK parity rules.
- SDK threat model: the security posture the specification's floor comes from.
sdk-developmentskill: loads both documents and drives the work, from locating the SDK checkouts through validation to raising the linked pull requests.
Read the specification before adding or changing an operation, configuration key, error, or UI component. A capability that lands here usually needs to land in the sibling SDKs too, and the pull request has to answer that question either way, by linking the sibling pull requests or by saying why they are not needed.
- Fix npm Vulnerability — Resolve a pnpm/npm security advisory.
Use when
pnpm auditor Dependabot surfaces a security advisory. Tries to update the head dependency first; falls back to a scoped override with a tracking GitHub issue. - Prune npm Overrides — Audit the pnpm overrides this repo
already carries and remove the ones upstream has since fixed. Use when asked to check, clean up, or prune
the overrides in
pnpm-workspace.yaml, or when an override's tracking issue comes up for review.
pnpm install
pnpm build # turbo run build across all packages
pnpm test # turbo run test
pnpm lint # turbo run lint (@thunderid/eslint-plugin)
pnpm typecheck # turbo run typecheck
pnpm format:check
# Scope to a single package:
pnpm --filter @thunderid/<pkg> run build
pnpm --filter @thunderid/<pkg> run test@thunderid/javascript is the framework-agnostic core (auth flows, token management, i18n, VendorConstants,
shared utils). Every other package depends on it, directly or transitively:
@thunderid/browser→@thunderid/javascript+ DOM/browser APIs (storage, session sync, WebAuthn).@thunderid/node→@thunderid/javascript+ server-side session/cookie handling.@thunderid/react,@thunderid/vue→@thunderid/browser.@thunderid/nextjs→@thunderid/node+@thunderid/react.@thunderid/nuxt→@thunderid/node+@thunderid/vue.@thunderid/express→@thunderid/node.@thunderid/react-router,@thunderid/tanstack-router→@thunderid/react.
Before adding a helper/util/constant to a framework package, check whether it belongs lower in this
hierarchy instead. If the same logic would be needed by more than one framework package (or you're about to
copy-paste one), it almost certainly belongs in @thunderid/javascript — or @thunderid/browser/
@thunderid/node if it needs DOM/server APIs — and should be re-exported from there, not duplicated. Every
package's src/index.ts re-exports its dependency's exports (export * from '@thunderid/javascript', etc.),
so check there before assuming a helper isn't already available.
The SDK is white-labelable: a consuming app can override the brand/vendor namespace via the vendor config
field, so storage keys, cookie names, CSS class prefixes, state keys, etc. shouldn't be pinned to one brand.
- Do not hardcode the literal
thunderid/ThunderID/THUNDERID(any casing) when building a runtime key/name that a consumer'svendoroverride should control — storage keys, cookie/session names, CSS class prefixes,useState/composable keys, log tags, DOMid/data-*attributes, and the like. - It's fine for the entry point — the main client class, provider component, or a file whose purpose IS
to represent the SDK itself (e.g.
ThunderIDClient,ThunderIDProvider,ThunderIDBrowserClient) — to carry the name. That's a fixed identity, not a per-tenant value. Don't flag those. - Avoiding the vendor name entirely is the best outcome, when the brand prefix isn't actually load-bearing.
- When a brand-scoped namespace is genuinely required, resolve it through
getVendorPrefix(vendor)(defined in@thunderid/javascript, re-exported via@thunderid/browser/@thunderid/node) instead of hardcoding a literal or repeating an inlinevendor ?? 'thunderid'fallback. That default belongs in one place.
- TypeScript, ESLint via
@thunderid/eslint-plugin, Prettier. - Every source file carries a copyright header (
@thunderid/copyright-headerESLint rule);samples/**is exempt (seeeslint.config.js). - Prefer editing or re-exporting an existing util over duplicating a helper across packages (see hierarchy above).
packages/
javascript/ Framework-agnostic core SDK
browser/ Browser/DOM layer (storage, WebAuthn, session sync)
node/ Server-side layer (cookies, sessions)
react/ React components + hooks
vue/ Vue components + composables
nextjs/ Next.js SDK (client + server)
nuxt/ Nuxt module
express/ Express middleware
react-router/ React Router integration
tanstack-router/ TanStack Router integration
samples/ Standalone quickstart demo apps, one per framework