@layerbase/secrets
v0.1.1
Published
Official TypeScript SDK for the Layerbase secret-store: fetch and decrypt project/environment secrets, plus an admin client.
Downloads
26
Maintainers
Readme
@layerbase/secrets
Official TypeScript SDK for the Layerbase secret-store: fetch and decrypt project and environment secrets, and (optionally) drive the admin API.
Secrets are encrypted at rest with age. The server only ever returns ciphertext; this SDK decrypts locally with the identity you hold, so a secret value never leaves your process.
The server itself lives in the private secret-store repo; this is the public client package (its source is public so npm can attach provenance to each publish).
Install
npm install @layerbase/secretsRuntime: Node.js 20 or newer. ESM only.
Consume: fetch and decrypt
A credential is a single string of the form st_v1.<token>.<identity>, minted once when an environment is created. Pass it to the client and pull your secrets:
import { SecretStoreClient } from '@layerbase/secrets'
const client = new SecretStoreClient({
host: 'https://secrets.layerbase.dev',
credential: process.env.SECRET_STORE_CREDENTIAL!,
})
const secrets = await client.pull('layerbase', 'production')
// { DATABASE_URL: '...', API_KEY: '...' }You can also construct the client from the token and identity separately:
const client = new SecretStoreClient({
host: 'https://secrets.layerbase.dev',
token: process.env.SECRET_STORE_TOKEN!,
identity: process.env.SECRET_STORE_IDENTITY!, // optional; see below
})Load straight into process.env
loadEnv pulls an environment and assigns each value into process.env. Existing keys are left untouched unless you pass override: true. It returns the keys it set.
const applied = await client.loadEnv('layerbase', 'production')
// applied: ['DATABASE_URL', 'API_KEY']
// with override: overwrite keys already present in process.env
await client.loadEnv('layerbase', 'production', { override: true })Other consume methods
// A single secret with its version.
const { value, version } = await client.get('layerbase', 'production', 'API_KEY')
// A subset of keys.
const some = await client.pull('layerbase', 'production', { keys: ['API_KEY'] })
// Raw armored ciphertext, no identity required (decrypt it yourself, later, elsewhere).
const ciphertext = await client.pullCiphertext('layerbase', 'production')The identity is optional. Without it, pullCiphertext still works (it returns armored ciphertext), but pull, get, and loadEnv throw a clear error because they cannot decrypt.
Admin
The admin client maps 1:1 onto the admin API behind the ADMIN_TOKEN bearer. Import it from the /admin subpath so the consume half stays out of your bundle when you do not use it:
import { SecretStoreAdmin } from '@layerbase/secrets/admin'
const admin = new SecretStoreAdmin({
host: 'https://secrets.layerbase.dev',
adminToken: process.env.SECRET_STORE_ADMIN_TOKEN!,
})
const { project, masterIdentity } = await admin.createProject({
name: 'Layerbase',
slug: 'layerbase',
})
// Persist masterIdentity NOW: it decrypts everything in the project and is never shown again.
const env = await admin.createEnvironment(project.id, { slug: 'production' })
// env.credential / env.token / env.identity are shown ONCE. Persist them now.
await admin.setSecret(env.environment.id, 'API_KEY', 'sk-live-...')
await admin.setSecrets(env.environment.id, { A: '1', B: '2' })
await admin.importDotenv(env.environment.id, 'A=1\nB="two words"')Other admin methods: listProjects, deleteProject, getEnvironment, deleteEnvironment, deleteSecret, createToken, revokeToken, audit.
Shown once
Several responses reveal a secret exactly once. The server never returns them again, so persist them at the call site:
createProject->masterIdentity(decrypts the whole project).createEnvironment->credential,token,identity.createToken->token.
Errors
Every failure throws a SecretStoreError with a numeric status and a stable machine-readable code:
import { SecretStoreError } from '@layerbase/secrets'
try {
await client.pull('layerbase', 'production')
} catch (error) {
if (error instanceof SecretStoreError && error.code === 'unauthorized') {
// rotate the credential
}
}code is one of 'unauthorized' | 'forbidden' | 'not_found' | 'invalid_request' | 'server_error' | 'network'. Error messages never contain a secret value, a token, an identity, or a raw response body.
Subpaths and tree-shaking
Two entry points resolve to separate files, each with its own types:
@layerbase/secrets-> the consume client (SecretStoreClient,parseCredential,SecretStoreError).@layerbase/secrets/admin-> the admin client (SecretStoreAdmin).
The package is sideEffects: false and ESM only, so a bundler drops the half you do not import.
Supported runtimes
Node.js 20+ (uses the built-in global fetch and process.env). ESM only; there is no CommonJS build.
Development
The package is self-contained: it has its own devDependencies pinned by package-lock.json, so its scripts never depend on globals.
npm ci # deps (typescript, tsx, @types/node, age-encryption)
npm run check # tsc --noEmit
npm run build # tsc -> dist/Some tests boot the real secret-store server, which lives in the private
Layerbase-LLC/secret-store repo, from a sibling checkout. For the full
end-to-end run, clone that repo next to this one (so it sits at ../secret-store)
and install its dependencies, or point SECRET_STORE_DIR at any checkout:
# ../secret-store, with `pnpm install` already run there
npm test # node:test via tsx; boots the real server and runs every test
# or an explicit path
SECRET_STORE_DIR=/path/to/secret-store npm testWhen no server checkout is resolvable, the server-dependent tests (the full
lifecycle suite and the live-decrypt step of the pack test) report as SKIPPED
with a message naming SECRET_STORE_DIR, and npm test still exits 0. The
server-independent checks always run: npm pack, installing the tarball into a
throwaway project, importing both subpaths, and the consumer-side tsc types
check.
CI (.github/workflows/publish.yml) does not check out the private server: it
runs the build, types, and pack/install checks, and the server tests skip. The
full end-to-end suite runs locally against a sibling checkout by design.
License
This SDK package (@layerbase/secrets) is released under the MIT license (see LICENSE). The MIT license applies to this SDK package only. The secret-store server itself remains proprietary and is not covered by this license.
