Version history for @goenvless/sdk, the TypeScript client for the Envless API. Pin it for the latest coverage, security patches and type fixes.
Looking for the runtime package that loads variables into your app? @goenvless/env has its own changelog. The Python and Ruby SDKs, both envless-sdk, and the PHP SDK, envless/sdk, have their own as well: Python, Ruby and PHP.
Next release
Each list method takes only the options its route reads. The shared ListOptions used to offer search, order and product on every list, and most routes ignored some of them. Every list now has its own options type, and the parity check compares each one with the query parameters of its route on every build.
members.list()gainsroleId, androles.list()gainssortBy(name,updatedorcreated)ListOptionsis now justlimit,offsetandsignal.SearchListOptions,SortedListOptions,ScopedListOptions,ScopedSortedListOptions,RoleListOptionsandMemberListOptionscover the rest, andDeliveryListOptions.statusis typed- Passing an option a route ignores is now a type error. Drop it: it never changed the result
variables.getById(),updateById(),deleteById(),versions.get()andversions.rollback()takeRequestScopeand no longer send theproductthey ignored
isSecret is part of VariableInput and VariablePatch. The API has always accepted it on variables.create(), createMany(), update() and updateById(); the types now say so. ApplyUpdateEntry gains name, which renames a variable inside variables.apply().
v0.0.3
No change to how the client behaves. Every method, type and error is the same as in 0.0.2. This release moves the links the package carries to their new home.
- The one-time update notice now points at
https://envless.cloud/docs/sdk/changelog - The README links to the dashboard at
envless.cloud/dashboardand to the docs atenvless.cloud/docs
Documentation corrections. Several method pages now describe the plan gates the API enforces.
- On the Free plan
versions.list(),listAll()anditerate()return only the latest version, andversions.get()on an older id orversions.rollback()fails with402 plan.upgrade_required. Publishing works on every plan - On the Free plan
variables.listHistory(),listAllHistory()anditerateHistory()return only the latest event. The API'shistoryLimitedandhiddenCountflags are not surfaced by the SDK yet, so a single event on Free is the plan, not an empty history members.invite()names the seat limits by plan: 1 member on Free and Dev, 15 on Team, no limit on Enterpriseworkspace.delete()no longer lists a live subscription as a guard. The subscription is canceled as part of the delete
v0.0.2
Deleting a product now deletes everything under it. products.delete() no longer refuses a product that still owns projects. The projects, their environments, every variable in them and every per-product access grant go in one transaction, so the 409 product.has_projects branch and the empty-first rule are gone from the API and from EnvlessApiError's vocabulary. projects.delete() and environments.delete() cascade the same way.
The tombstones report what went. DeletedProductResource gained deletedProjects, deletedEnvironments and deletedVariables; DeletedProjectResource gained deletedEnvironments and deletedVariables; DeletedEnvironmentResource gained deletedVariables. Purely additive for readers, but the fields are required, so any fixture or mock that constructs one of these types by hand needs updating.
- Version snapshots are neither deleted nor counted. They stop resolving once their environment is gone
DELETEis still retried on a 408, a 429 or a 5xx, a gateway timeout included, and the cascade may commit before a lost response, so a retry answeringproduct.not_foundmost likely means the first attempt succeeded- Creating or duplicating a child while its parent is being deleted now fails with a
409carryingproduct.not_found,project.not_foundorenvironment.not_foundrather than leaving an orphan behind
Variable names are checked before the request leaves your machine. Every write that carries a name, create, createMany, update, updateById and the creates in apply, now validates it locally against the rule the API enforces: 1 to 100 characters of letters, digits and underscores, never starting with a digit. A bad name costs no round trip.
- The refusal rejects the returned promise with an
EnvlessApiErrorcarryingstatus: 400,code: 'request.body_invalid'andfield: 'name', the same vocabulary the API answers with.requestIdisundefined, which is how you tell a local refusal from a server one - It rejects, it does not throw synchronously, so
.catch()andtry/awaitboth work the way they do for every other failure in this client createManyrefuses the whole batch on one unusable name rather than reporting it inskipped, so a name that would have been skipped is now a rejection you see immediately- New export
variableNameValidation(name)returns{ valid, error }without throwing, for pre-checking a name in your own UI or importer
Variable names are case reserved, not case normalised. A name is stored and returned with exactly the casing you send, but uniqueness and lookup both ignore case. envless.variables.get('api', 'production', 'api_key') resolves a live API_KEY, and creating api_key alongside it is 409 variable.name_conflict rather than a second variable. The same holds for update, delete, deleteMany and the history routes.
Actor attribution now names the API key. VariableHistoryEvent.actor, VersionResource.createdBy and the actor on a verified WebhookEvent share one ActorReference shape and gained a key field, typed ActorKeyReference | null, carrying the key's id, name and prefix. A write made by an API key is attributed to the key rather than to whoever happens to own it.
- Additive for readers, but the field is required on the actor object, so any fixture or mock that builds one of these by hand needs updating
ActorReferenceandActorKeyReferenceare exported types
Documentation corrections. Several method pages described behaviour the API does not have. variables.createMany() does not resolve with count: 0 when everything is refused, it rejects with a 400; a total failure is not a 500. Name lookups were documented as case sensitive and are not. keys.list() is refused outright for a key fenced to specific projects rather than returning the full list. webhooks.update()'s example used an event type that does not exist, version.published, in place of variable_version.created. The webhook retry schedule is five attempts at 30s, 2m, 10m then 30m, not a sixth at 1h.
v0.0.1
Initial release. A standalone, zero-dependency TypeScript client covering 100% of the public HTTP API: 113 methods across 13 namespaces, over all 80 documented operations.
Client
init({ token?, ... })configures the SDK once at startup, andenvlessis a live handle you can import anywhere without threading an instance through your app. Nothing is resolved until the first method call, so import order does not matterinitis optional: withENVLESS_TOKENset, the first call onenvlessbuilds the client itselfgetClient()returns the real default instance, andresetClient()drops it, which is what test teardown wantscreateClient({ token?, baseUrl?, maxRetries?, timeoutMs?, userAgent?, fetch? })for an explicit instance, or several at once when you need more than one workspacenew Envless({ token, ... })reads no environment at all, for Workers and bundles withoutprocess- Namespaces:
me,workspace(withworkspace.domain),workspaces,products(withproducts.access),projects,environments,variables,versions,members,invites,roles,keys,webhooks raw.request(path, options)as an escape hatch that keeps envelope unwrapping, the retry policy and the typed errors- Every method accepts
options.signalfor cancellation
Pagination
- Every collection has three methods:
listfor one page,listAllfor everything,iteratefor a lazyAsyncGenerator - Secondary collections get their own trio:
products.listProjects,products.access.list,variables.listHistory,versions.listEntries,webhooks.listDeliveries, each withlistAll*anditerate*forms - The walk terminates on
hasMore: falseor an empty page, and advances by items actually received rather than bylimit roles.listPermissionsandwebhooks.listEventTypesare deliberately not paginated
Errors
EnvlessApiErrorcarries the stablecodeplusstatus,resource,field,requestIdandretryAfterSeconds, with getters for the common branches:isAuth,isScopeMissing,isValidation,needsUpgrade,isNotFound,isConflict,isRateLimitedEnvlessNetworkErrorfor transport failures, timeouts and aborts- Retries
GET,HEAD,PUTandDELETEon408,429and5xxwith exponential backoff capped at 30 seconds, honouringRetry-After.POSTandPATCHare never retried, so a write that timed out is never silently duplicated
Encryption
encryptValue/decryptValueagainst the workspace key, byte-for-byte compatible with the@goenvless/clibinary, the dashboard and the@goenvless/envruntimedecryptValuereads the PBKDF2 iteration count out of av2:<iterations>:envelope and derives at that cost, rather than assuming the 200,000 default. A malformed count is refused instead of being silently retried at the defaultderiveWorkspaceKeyto derive once and reuse across a batch, instead of paying 200,000 PBKDF2 iterations per variableimportWorkspaceKeyfor a raw base64url AES-256 machine keyisCiphertextandENCRYPTION_PARAMETERSrotateWorkspacePassphrase(...)walks live variables and version entries, in batches of 100. Rotating only the live values silently breaks every later rollback, because history stays on the old key
Webhooks
verifyWebhookSignature({ payload, headers, secret, toleranceSeconds? })verifies the HMAC-SHA256 signature overid.timestamp.body, checks timestamp drift, and returns the parsed event- Accepts a
Headersinstance or a plain object, matching header names case-insensitively, and tolerates a space-separated list of signatures - Comparison is constant-time, and it uses Web Crypto only so it runs at the edge
Runtime
- No dependencies, and no Node built-ins. Base64 goes through
btoa/atobrather thanBuffer, and cryptography throughcrypto.subtle - Runs on Node 20+, Bun, Deno and Cloudflare Workers
- ESM and CommonJS builds with per-condition
.d.tsand.d.cts, andsideEffects: falseso bundlers can drop unused namespaces
Verification
- A parity check cross-references the method manifest against
openapi.jsonin both directions and fails the build on any gap, mismatchedoperationId, or mismatched scope declaration