@samveys/core

Version
1.0.3Nov 5, 2026
Install
pnpm add @samveys/core

From packages/core/README.md in the framework repository.

@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.

ts
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 sameDatabase

A dependency cycle fails during resolution and includes the full path, for example:

text
SamveysError [SVS_E001]: Dependency cycle: A → B → C → A

3. 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.

ts
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.

ts
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.

ts
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 keyChannel
TRACING_CHANNELS.httpRequestsamveys:http.request
TRACING_CHANNELS.dbQuerysamveys:db.query
TRACING_CHANNELS.viewRendersamveys:view.render
TRACING_CHANNELS.jobProcesssamveys:job.process
TRACING_CHANNELS.commandRunsamveys:command.run

A subscriber needs no Samveys import. Node’s tracingChannel() publishes phase channels with the tracing: prefix:

ts
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.

ts
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 name

8. 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.

ts
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.

ts
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 valid

10. 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.

ts
import { SamveysError, toProblemDetails } from '@samveys/core'

const error = new SamveysError('SVS_E040', 'Record not found')
const problem = toProblemDetails(error, { requestId: 'req-1' })
json
{
  "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:

ExportPurpose
ERROR_CODESStable framework error code registry and descriptions.
SAMVEYS_API_VERSIONPlugin API compatibility version.
TRACING_CHANNELSPublic tracing channel names.
SamveysErrorFramework error class with code and metadata.
explainLook up error-code information.
isSamveysErrorType guard for framework errors.
pluginErrorAdd plugin context to a lifecycle error.
toProblemDetailsConvert an error to Problem Details.
InjectionTokenCreate a typed non-class DI token.
createContainerCreate a DI container.
injectResolve from the active container context.
createRequestContextCreate per-request context data.
currentContextRead the active request context.
markRequestBoundMark a service as request-bound.
requireContextRequire an active context.
runWithContextRun asynchronous work with a request context.
warnIfCapturedDetect captured request-bound services.
createHookRegistryCreate a typed hook registry.
traceAsyncRun async work inside a Samveys tracing span.
tracingChannelsAccess the published Node tracing channels.
EventEmitterCreate the typed application event emitter.
createLoggerCreate a structured, redacting JSON logger.
EnvBuild environment variable rules.
defineEnvDefine and type an environment schema.
envRead a validated environment value.
loadEnvLoad and validate environment values.
configRead a value from the active app config.
createConfigRepositoryCreate a read-only config repository.
defineConfigType a samveys.config.ts value.
loadConfigDirLoad config files from a directory.
mergeConfigSafely deep-merge config trees.
createEncrypterCreate an AES-256-GCM encrypter.
createHasherCreate an Argon2id password hasher.
definePluginDefine a plugin factory.
defineProviderDefine a provider.
resolvePluginOrderResolve and validate plugin load order.
satisfiesRangeCheck a version against a supported range.
createExtensionRegistryCreate the application extension registry.
ConfigTokenTyped token for the config repository.
EncrypterTokenTyped token for the encrypter.
EventsTokenTyped token for the event emitter.
HasherTokenTyped token for the password hasher.
HooksTokenTyped token for the hook registry.
LoggerTokenTyped token for the logger.
createAppCreate an application and its lifecycle.