TypeScript/Node.js client and CLI for the Sanshain Service.
Sanshain (Japanese for "Sunshine") is a specialized REST service designed to manage, split, and distribute API specifications (OpenAPI, AsyncAPI, gRPC/Proto). SanshainJS provides a seamless way for TypeScript-based microservices to publish their API contracts and consume only the specific endpoints they need.
Client 3.x speaks Sanshain Service 2.x. Sanshain 2.0 replaced the branch model with producer-declared versions, and SanshainJS 3.x is a clean break to match: it talks only the 2.0 wire contract.
- The version lives in the spec file. OpenAPI/AsyncAPI:
info.version. Proto: a mandatory// sanshain-version: MAJOR.MINOR.PATCHcomment. Strict three-part semver, novprefix, no suffixes. - Consumers pin exact versions. Every
requiresentry carries aversion; there is no fallback and nothing waits. - Stability is a switch, not git magic. Every provide is a
snapshotunless you explicitly pass--ga(or setSANSHAIN_GA=true).
If you point this client at a pre-2.0 Sanshain server, it will detect that (one lazy GET /version after a failure) and tell you to upgrade the server.
- CLI Tool: Easy-to-use commands for
provideandrequireoperations. - Maven-like Lifecycle: Easily integrates into
package.jsonscripts for automatic build-time updates. - Deduplication: Automatically handles shared DTOs using Sanshain's bundle API.
- Version Pinning:
requiresentries pin exact producer versions — builds are reproducible by construction. - Stability Switch:
snapshotby default;--ga/SANSHAIN_GA=truefor immutable GA publishes (set it on protected-branch pipelines). - GZIP Compression: Efficient data transfer for large specifications.
- CI/CD Ready: Built-in support for any CI/CD platform (GitLab, Jenkins, GitHub, etc.).
- Content Caching: Skips provide when spec file is unchanged (SHA-256 hash match).
- ETag Caching: Skips require when server spec is unchanged (304 Not Modified).
- Strict Mode: Optional strict validation for CI environments.
SanshainJS acts as a bridge between your microservices. It follows a "Download-then-Generate" pattern:
- Configure: Define which services you need — and at exactly which version — in
sanshain.yaml. - Download: Run
sanshain require. The CLI fetches the pinned endpoint snippets from the Sanshain Service and saves them as.yaml(or.proto) files in your localoutputDirectory. - Generate: Use a tool like
openapi-typescriptto turn those local.yamlfiles into.tscode. - Develop: Your application imports the generated TypeScript code with full autocomplete and type safety.
This ensures your service only knows about the specific endpoints it actually uses, at the exact versions it pinned.
npm install --save-dev sanshainjsSanshainJS uses the standard sanshain.yaml configuration file. Create this file in your project root:
sanshainUrl: http://localhost:3000
serviceName: my-ts-service
compression: true
provides:
- file: src/docs/openapi.yaml # version is read from the file's info.version
requires:
- serviceName: auth-service
version: 1.2.0 # exact pin — MAJOR.MINOR.PATCH, no ranges, no latest
outputDirectory: src/generated/auth
endpoints:
- method: GET
path: /api/v1/users
- method: POST
path: /api/v1/loginThe provide version is never configured here — it is read from the spec file itself (info.version for OpenAPI/AsyncAPI, the // sanshain-version: MAJOR.MINOR.PATCH comment for proto).
Branch-era fields (branch, timeout, baseVersion, releaseBranches) are rejected at parse time with a migration hint. A requires entry without a version is a hard error — list a producer's available versions with GET /producers/<name>/versions.
| Variable | Description |
|---|---|
SANSHAIN_URL |
Override sanshainUrl |
SANSHAIN_TOKEN |
Bearer token for authentication |
SANSHAIN_SERVICE_NAME |
Override serviceName |
SANSHAIN_GA |
Set to true to provide as GA instead of snapshot |
SANSHAIN_BEST_EFFORT |
Set to true to continue on errors |
SANSHAIN_STRICT |
Set to true to fail on missing configuration |
Every provide is a snapshot by default: overwritable work-in-progress that expires when unused. To publish an immutable GA version, flip the switch explicitly:
sanshain provide --ga
# or
SANSHAIN_GA=true sanshain provideThere is no git or branch detection — CI simply sets SANSHAIN_GA=true on protected-branch pipelines, and that is the whole mechanism. GA versions permanently claim their number; re-providing a GA version with different content is rejected (see below).
To make SanshainJS behave like the Maven plugin (running automatically during build), add it to your package.json:
"scripts": {
"prebuild": "sanshain require",
"build": "tsc"
}Now, every time you run npm run build, it will first download the pinned OpenAPI specs.
Upload your service's API specification to Sanshain under the version declared in the spec file:
sanshain provide # snapshot (default)
sanshain provide --ga # immutable GADownload the pinned endpoint snippets and bundles as defined in sanshain.yaml:
sanshain requireSanshain 2.0 fails fast and self-service; the CLI surfaces each case distinctly:
-
404(Unknown): the producer, or the pinned version, does not exist on the server — in either stability. A configuration error; fix the pin (list available versions:GET /producers/<name>/versions). Nothing waits for a version to appear. -
410(Absent): the pinned version exists but deliberately does not include the requested endpoint(s). For bundles, the whole bundle fails and the server names the missing endpoints. -
409(Version rules): the provide was rejected — most commonly "same GA version, different content". The server proposes the next free version, and the CLI prints it prominently:Provide rejected by the version rules (409): version 1.2.0 is GA and immutable Publish as 1.3.0 — update info.version in src/docs/openapi.yamlThe CLI never modifies your spec files — bump the version yourself and re-run.
-
Pre-2.0 server: after a failed provide/require the CLI checks
GET /versiononce; if the instance is older than 2.0.0 you get a clear "upgrade the server" message instead of a confusing wire error.
Before uploading, the CLI computes the SHA-256 hash of the spec file and compares it with the cached hash from the last provide. If unchanged:
⏭ Spec unchanged (hash match), skipping provide.
Use --force (or SANSHAIN_FORCE=true) to re-provide anyway. Server-side, re-providing byte-identical content is an idempotent no-op, so CI re-runs never fight.
The CLI stores the ETag from require responses and sends If-None-Match on subsequent runs. On 304 Not Modified:
⏭ user-service spec unchanged (304), skipping code generation.
A pin on a GA version can never change content; a pin on a snapshot can — which is exactly what the ETag detects.
The local cache is stored at node_modules/.cache/sanshain/state.json with the following format:
{
"provides": {
"openapi.yaml": {
"content_hash": "sha256:abc123...",
"version": "1.4.0",
"last_provided": "2026-04-25T12:00:00Z"
}
},
"requires": {
"user-service|1.2.0|GET|/api/v1/users": {
"etag": "\"sha256:def456...\"",
"last_fetched": "2026-04-25T12:00:00Z"
}
}
}By default, the CLI warns and skips when configuration is incomplete (no serviceName, no provides, no requires). This makes it safe to include both commands in build scripts even if only one applies.
To fail on missing configuration, enable strict mode:
strict: trueOr via environment variable:
export SANSHAIN_STRICT=trueWhen strict mode is enabled:
- Missing
serviceName→ exit with error - No
providesconfigured → exit with error - No
requiresconfigured → exit with error
SanshainJS is platform-agnostic. For detailed guides on integrating it into your CI/CD pipeline (GitLab, Jenkins, GitHub Actions, etc.), see:
This project is licensed under the Apache License 2.0. See the LICENSE file for details.