@samveys/core
Dari packages/core/README.md di repository framework.
Halaman ini belum diterjemahkan. Kamu sedang membaca versi bahasa Inggris.
@samveys/core provides the application lifecycle and shared runtime primitives: dependency injection, plugins and providers, request context, hooks, validated environment and configuration, logging, cryptography, and framework errors. Its public API is the package entry point (@samveys/core). Requires Node.js ≥ 26.13.0 and ESM. The examples below are checked against the public exports and types (tested).
1. Install
Samveys packages are internal-only. Install them through the organization’s configured restricted registry and workspace tooling; do not publish or install them through the public npm registry. In this monorepo, use the workspace package directly. The package requires Node.js ≥ 26.13.0.
2. Container and inject()
createContainer() constructs a container. Concrete zero-argument classes can be resolved without registration; they are singletons by default. Use singleton() for a shared factory, transient() for a fresh value per resolution, and instance() for an existing value. swap() replaces a binding and restore() reinstates it, which is useful in tests. Dispose the container to release resolved services that implement dispose() or [Symbol.asyncDispose]().
inject() is for class field initializers resolved by the container, or code running within container.run(). It is not a general constructor-parameter injector.
import { createContainer, inject } from '@samveys/core'
class Database {}
class Users {
database = inject(Database)
}
const container = createContainer()
.singleton(Database, () => new Database())
.transient(class RequestMarker {})
const users = container.make(Users)
const sameDatabase = container.make(Database)
container.swap(Database, () => new Database())
container.restore(Database)
await container.dispose()
void users
void sameDatabaseA dependency cycle fails during resolution and includes the full path, for example:
SamveysError [SVS_E001]: Dependency cycle: A → B → C → A3. Request context
createRequestContext() creates a context with a request ID, start time, and locals map. runWithContext(context, fn) runs work inside its asynchronous context; currentContext() reads the active context. The HTTP package also supplies ctx.signal, which is aborted on disconnect or timeout. Use the context helpers where request-local data must follow asynchronous work rather than storing it in singleton services.
import { createRequestContext, currentContext, runWithContext } from '@samveys/core'
const requestContext = createRequestContext({ requestId: 'req-1' })
await runWithContext(requestContext, async () => {
currentContext()?.locals.set('tenant', 'north')
})4. Providers and lifecycle
Providers use defineProvider({ name, register, boot, start, ready, shutdown }). The application lifecycle is register → boot → start → ready → shutdown. Register services during register; resolving another binding at that stage is rejected. Shutdown runs in reverse registration order and disposes the container.
import { createApp, defineProvider, InjectionToken } from '@samveys/core'
const metricsEnabled = new InjectionToken<boolean>('metrics.enabled')
const metrics = defineProvider({
name: 'metrics',
register({ container }) {
container.instance(metricsEnabled, true)
},
ready({ logger }) {
logger.info('Metrics provider ready')
},
})
const app = createApp({ providers: [metrics], processEnv: {} })
await app.run()
await app.shutdown()5. Plugins
definePlugin() creates a plugin factory. Register plugin instances explicitly through createApp({ plugins: [...] }) or the application configuration. requires expresses Samveys API and plugin dependencies. The optional Standard Schema config validates options before register; lifecycle hooks receive only the public PluginContext (container, hooks, extend, config, logger, app). Do not resolve services during register.
import { createApp, definePlugin } from '@samveys/core'
class AuditLog {
readonly entries: string[] = []
}
const audit = definePlugin({
name: '@acme/audit',
requires: { samveys: '^1.0' },
register({ container }) {
container.singleton(AuditLog)
},
boot({ container, hooks, extend }) {
const log = container.make(AuditLog)
hooks.on('app:ready', () => {
log.entries.push('ready')
})
extend.command({ name: 'audit:prune', description: 'Prune audit records' })
extend.context('audit', () => log)
},
})
const app = createApp({ plugins: [audit()], processEnv: {} })
await app.run()
await app.shutdown()6. Hooks and tracing channels
Hooks run sequentially in registration order. on() invokes every listener and ignores return values; filter() passes a value through listeners; intercept() stops at the first non-undefined result. Unsubscribe functions remove listeners.
The five node:diagnostics_channel tracing channel names are:
| Export key | Channel |
|---|---|
TRACING_CHANNELS.httpRequest | samveys:http.request |
TRACING_CHANNELS.dbQuery | samveys:db.query |
TRACING_CHANNELS.viewRender | samveys:view.render |
TRACING_CHANNELS.jobProcess | samveys:job.process |
TRACING_CHANNELS.commandRun | samveys:command.run |
A subscriber needs no Samveys import. Node’s tracingChannel() publishes phase channels with the tracing: prefix:
import { subscribe, unsubscribe } from 'node:diagnostics_channel'
const channel = 'tracing:samveys:http.request:start'
const onStart = (message: unknown): void => {
console.log('HTTP request span started', message)
}
subscribe(channel, onStart)
// When finished observing:
unsubscribe(channel, onStart)7. Env and config
defineEnv() declares validated environment rules using Env.string(), Env.number(), Env.boolean(), Env.url(), Env.secret(), or Env.enum(). loadEnv() reads .env, then .env.[APP_ENV], then process values (process values win). Passing the definition to createApp({ env }) validates it during registration. Augment EnvValues to type env('KEY').
defineConfig() types an application config object. Config files under config/*.ts are loaded when createApp is given a root directory (unless disabled); inline config merges over them. config(path) reads the current application’s validated config; augment Config to type allowed paths.
import { Env, config, createApp, defineEnv, env } from '@samveys/core'
declare module '@samveys/core' {
interface EnvValues {
PORT: number
}
interface Config {
app: { name: string }
}
}
const environment = defineEnv({ PORT: Env.number({ default: 3333, int: true }) })
const app = createApp({ env: environment, config: { app: { name: 'catalog' } }, processEnv: {} })
await app.register()
const port: number = env('PORT')
const name: string = config('app.name')
void port
void name8. Logger and redaction
createLogger() writes one JSON record per line to its destination (stdout by default). Configure the level, bindings, destination, and additional redaction keys or dotted paths. Password, secret, token, authorization, cookie, API-key, and related keys are redacted by default, case-insensitively at any depth.
import { createLogger } from '@samveys/core'
const logger = createLogger({ level: 'info', redact: ['user.ssn'] })
logger.info({ password: 'hidden', user: { ssn: 'hidden' } }, 'Sign-in')9. Encryption and hashing
createEncrypter() uses AES-256-GCM. Keys must be base64: followed by 32 random bytes; purpose is authenticated with the ciphertext. Decryption may try configured previous keys for rotation. createHasher() uses Argon2id and returns PHC strings; verification uses constant-time comparison and verifyAndRehash() can produce an updated hash.
import { createEncrypter, createHasher } from '@samveys/core'
const key = `base64:${Buffer.alloc(32, 7).toString('base64')}`
const encrypter = createEncrypter({ key })
const sealed = encrypter.encrypt('value', 'session-cookie')
const plain = encrypter.decrypt(sealed, 'session-cookie')
const hasher = createHasher()
const hash = await hasher.make('correct horse battery staple')
const valid = await hasher.verify(hash, 'correct horse battery staple')
void plain
void valid10. Errors
SamveysError carries a stable SVS_E* code, status, optional cause and metadata. isSamveysError() narrows framework errors, explain() returns the registered code description, and toProblemDetails() converts an error into the RFC 9457 Problem Details shape. HTTP responses use application/problem+json and omit internal error details unless the error is exposed or debug mode permits it.
import { SamveysError, toProblemDetails } from '@samveys/core'
const error = new SamveysError('SVS_E040', 'Record not found')
const problem = toProblemDetails(error, { requestId: 'req-1' }){
"type": "urn:samveys:error:SVS_E040",
"title": "Not Found",
"status": 404,
"detail": "Record not found",
"code": "SVS_E040",
"requestId": "req-1"
}11. Reference
Value exports from @samveys/core:
| Export | Purpose |
|---|---|
ERROR_CODES | Stable framework error code registry and descriptions. |
SAMVEYS_API_VERSION | Plugin API compatibility version. |
TRACING_CHANNELS | Public tracing channel names. |
SamveysError | Framework error class with code and metadata. |
explain | Look up error-code information. |
isSamveysError | Type guard for framework errors. |
pluginError | Add plugin context to a lifecycle error. |
toProblemDetails | Convert an error to Problem Details. |
InjectionToken | Create a typed non-class DI token. |
createContainer | Create a DI container. |
inject | Resolve from the active container context. |
createRequestContext | Create per-request context data. |
currentContext | Read the active request context. |
markRequestBound | Mark a service as request-bound. |
requireContext | Require an active context. |
runWithContext | Run asynchronous work with a request context. |
warnIfCaptured | Detect captured request-bound services. |
createHookRegistry | Create a typed hook registry. |
traceAsync | Run async work inside a Samveys tracing span. |
tracingChannels | Access the published Node tracing channels. |
EventEmitter | Create the typed application event emitter. |
createLogger | Create a structured, redacting JSON logger. |
Env | Build environment variable rules. |
defineEnv | Define and type an environment schema. |
env | Read a validated environment value. |
loadEnv | Load and validate environment values. |
config | Read a value from the active app config. |
createConfigRepository | Create a read-only config repository. |
defineConfig | Type a samveys.config.ts value. |
loadConfigDir | Load config files from a directory. |
mergeConfig | Safely deep-merge config trees. |
createEncrypter | Create an AES-256-GCM encrypter. |
createHasher | Create an Argon2id password hasher. |
definePlugin | Define a plugin factory. |
defineProvider | Define a provider. |
resolvePluginOrder | Resolve and validate plugin load order. |
satisfiesRange | Check a version against a supported range. |
createExtensionRegistry | Create the application extension registry. |
ConfigToken | Typed token for the config repository. |
EncrypterToken | Typed token for the encrypter. |
EventsToken | Typed token for the event emitter. |
HasherToken | Typed token for the password hasher. |
HooksToken | Typed token for the hook registry. |
LoggerToken | Typed token for the logger. |
createApp | Create an application and its lifecycle. |