A vendor-neutral TypeScript foundation for Node.js services. It provides capability contracts, configuration resolution, structured logging, bounded retries, security helpers, and consistent service responses without coupling business logic to a cloud provider, framework, or runtime.
API reference · Developer documentation · Changelog · Contributing
The package is published to GitHub Packages. Authenticate with a GitHub personal access token that has the
read:packages permission:
npm login --scope=@bayudwiyansatria --auth-type=legacy --registry=https://npm.pkg.github.com
Configure the package scope in the consuming project's .npmrc:
@bayudwiyansatria:registry=https://npm.pkg.github.com
Then install the package:
npm install @bayudwiyansatria/core
Never commit a personal access token. Supply registry credentials through your local npm configuration or
a CI secret such as NPM_AUTH_TOKEN.
Configure the shared settings once during application startup, then create a structured logger:
import { configure, Logger, systemDefaults } from '@bayudwiyansatria/core'
configure(systemDefaults)
const logger = Logger.fromEnv(
{
LOG_LEVEL: 'info',
SERVICE_NAME: 'article-api'
},
{ component: 'ArticleService' }
)
logger.info('service.started')
Applications using a platform adapter can combine its defaults with the system defaults:
import { configure, systemDefaults } from '@bayudwiyansatria/core'
import { platformDefaults } from '<adapter-package>'
configure({ ...systemDefaults, ...platformDefaults }, overrides)
configure() registers defaults and optional overrides. resolve() reads the resulting settings from anywhere in the
application:
import { configure, resolve, systemDefaults } from '@bayudwiyansatria/core'
import type { LoggingSettings } from '@bayudwiyansatria/core'
configure(systemDefaults, {
logging: { service: 'article-api' }
})
const logging = resolve<LoggingSettings>('logging')
Overrides merge by field, so partial overrides retain unspecified defaults. Fields set to undefined are ignored. Call
configure() before resolving settings, and avoid calling resolve() at module scope in adapters. See the
configuration guide for initialization order, merge behavior, and troubleshooting.
Logger writes flat JSON lines suitable for log aggregation and indexing:
const logger = Logger.fromEnv(process.env, { component: 'ArticleService' })
logger.info('article.created', { id: 42 })
logger.error('external_api.failed', { status: 502 }, 'Upstream service returned an error')
const requestLogger = logger.child({ requestId: 'req-123' })
Each log call separates the stable event name, structured data, and human-readable message. Ambient context fields can
be selected with LOG_FIELDS:
LOG_FIELDS="requestId,method,path" # selected fields
LOG_FIELDS="*" # all fields (default)
LOG_FIELDS="" # no ambient context fields
Applications program against small, runtime-neutral interfaces. Adapter packages bind those interfaces to concrete infrastructure.
| Interface | Responsibility |
|---|---|
CacheStore |
Key/value caching |
CoordinationStore |
Locks and coordination |
DataStore |
Document or record storage |
InferenceEngine |
Model inference |
MessageQueue |
Message publishing/handling |
ObjectStore |
Object and blob storage |
RateLimiter |
Request throttling |
SqlConnectionProvider |
Relational connections |
TelemetrySink |
Metrics and request facts |
VectorIndex |
Vector search |
No-op implementations are available for cache, messaging, rate limiting, and telemetry. They are useful in tests and in deployments where those capabilities are intentionally disabled.
Extend Service to return the same APIResponse shape across service methods:
import { Service } from '@bayudwiyansatria/core'
import type { APIResponse } from '@bayudwiyansatria/core'
export class ProfileService extends Service {
public async get(id: string): Promise<APIResponse> {
const profile = await this.repository.find(id)
return profile ? this.ok('Profile found', profile) : this.fail('Profile not found')
}
}
Use fail() for expected negative outcomes that callers can act on. Throw errors for unexpected faults so application
error handling can surface them appropriately.
Retry.run() retries transient failures with exponential backoff. The default budget comes from delivery.retries and
delivery.retryBackoffMs:
import { Retry } from '@bayudwiyansatria/core'
const rates = await Retry.run(() => client.rates(), {
logger,
event: 'rates.retry'
})
The built-in policy retries rate limits, selected server errors, and network failures. Abort and timeout errors are not
retried. Only retry operations that are safe to repeat; callers can provide isRetryable for domain-specific rules.
The package includes focused helpers for common service concerns:
import { Json, List, redact, Signature, Text, Time, timingSafeEqual } from '@bayudwiyansatria/core'
| Export | Purpose |
|---|---|
redact |
Remove credential-like fields before logging data |
timingSafeEqual |
Compare strings without leaking timing information |
Signature |
Create and verify HMAC-SHA-256 signatures |
Json |
Parse untrusted JSON without throwing |
List |
Split lists into bounded chunks |
Text |
Trim, truncate, and test blank strings |
Time |
Read timestamps and calculate elapsed time |
Everything exported from src/index.ts is public API.
| Category | Main exports |
|---|---|
| Services | Service, APIResponse |
| Capabilities | Capability, ten capability interfaces, four no-op implementations |
| Configuration | configure, resolve, systemDefaults, SystemConfiguration, Overrides, settings types |
| Observability | Logger, LogFields, LogContext, LogLevel, LoggingSettings |
| Resilience | Retry, RetryOptions |
| Security | redact, timingSafeEqual |
| Errors | ConfigurationError, MissingCapabilityError |
| Utilities | Json, List, Signature, Text, Time |
For signatures and detailed behavior, visit the generated API reference.
This package is the innermost layer of a service architecture. It deliberately does not depend on vendor SDKs, web frameworks, or platform-specific runtime types.
Application and business logic
|
v
@bayudwiyansatria/core capability contracts and shared primitives
^
|
Adapter packages vendor and runtime implementations
The source tree reinforces that boundary:
src/
core/ Configuration, logging, services, retries, and no-op capabilities
types/ Public contracts and configuration shapes
constants/ Runtime-neutral defaults
exceptions/ Public error classes
security/ Security-oriented helpers
utils/ Pure, vendor-neutral utilities
index.ts Public API barrel
types/, constants/, exceptions/, security/, and utils/ are leaf layers and cannot import from core/. ESLint
enforces this dependency direction and the vendor-neutral boundary.
Adapter packages must keep @bayudwiyansatria/core external rather than bundling it. Configuration uses module state,
and loading a second embedded copy would create a separate registry and break error class identity.
Development and CI use Node.js 22.
git clone https://github.com/bayudwiyansatria/nodejs-core.git
cd nodejs-core
npm ci
npm test
npm run build
Common commands:
| Command | Purpose |
|---|---|
npm test |
Run ESLint and Jest with coverage |
npm run test:run |
Run Jest with coverage |
npm run lint:run |
Check source files with ESLint |
npm run format |
Format source, tests, configuration, and Markdown |
npm run build |
Build ESM, CommonJS, UMD, and declaration artifacts |
npm run build:docs |
Generate the TypeDoc API reference |
npm run build:docs:book |
Generate the HonKit developer guide |
npm run build:static |
Build documentation, coverage, and the landing page |
Generated lib/ and dist/ output is intentionally not committed.
The static documentation site combines the TypeDoc API reference, HonKit developer guide, and Jest coverage report.
Build it locally with npm run build:static or serve it through Docker with npm run docker:serve:docs.
Contributions are welcome. Before opening a pull request, read the contributing guide and Code of Conduct.
This project follows Semantic Versioning.
Licensed under the MIT License.