Packages
Core
Reference for @knead-js/core, the dependency injection container, module system, lifecycle, metadata and logging primitives that every Knead package builds on.
On this page 62 sections
- Application context
- ApplicationContext
- CreateContextOptions
- Modules
- Module()
- ModuleOptions
- DynamicModule
- ModuleImport and ModuleExport
- The MODULES token
- ModuleRef
- ModuleInjector
- Injection
- inject()
- InjectOptions
- runInInjectionContext()
- isInInjectionContext()
- InjectorLike
- Injectable()
- markInjectable()
- InjectableOptions
- Tokens
- InjectionToken
- Token
- Type and AbstractType
- tokenName()
- DEVELOPMENT
- Providers
- Provider
- Scope
- normalizeProvider() and NormalizedProvider
- Lifecycle hooks
- Errors
- Metadata
- MetadataKey
- Class metadata
- Method metadata
- INJECTABLE and MODULE_METADATA
- Logging
- LOGGER and Logger
- ConsoleLogger
- MemoryLogger
- DeferredLogger and WarnLogger
- LogEntry and LogLevel
- logError() and ErrorLogOptions
- redactCredentials()
- redactValue()
- summarizeError()
- logValue()
- escapeControl()
- errorText() and isPlainObject()
- LOGGED_MESSAGE_LENGTH and MAX_LOGGED_VALUE_LENGTH
- Network helpers
- parseIp()
- isLoopbackHostname()
- plaintextRedisHost()
- defaultRedisUrl()
- sendRedisCommand()
- ownedRedisClientOptions()
- Durations
- Duration
- parseDuration() and ParseDurationOptions
- MAX_DURATION_MS
bun add @knead-js/core@knead-js/core is the container that the rest of Knead runs on: inject() and tokens, the
@Module() graph with its encapsulation rules, providers and scopes, lifecycle hooks and
ApplicationContext, the decorator metadata helpers, and the logging primitives (the LOGGER
token and the redaction functions) that every package logs through. It has no dependencies. The
other @knead-js/* packages, except @knead-js/sanitize, list it as a peer dependency, so install
it alongside them.
The guides explain how the pieces fit together: modules, dependency injection, lifecycle and logging. This page lists every export.
#Application context
#ApplicationContext
The application container. KneadFactory.create() from @knead-js/http and @knead-js/testing
build one internally; use it directly for applications without HTTP. See
standalone applications.
| Member | Signature | Description |
|---|---|---|
create | static create(root: ModuleImport, options?: CreateContextOptions): Promise<ApplicationContext> | Builds the module graph, runs every singleton factory (awaiting async ones), creates every remaining singleton, then runs onModuleInit and onApplicationBootstrap. Rejects on any error; if a startup hook fails, the instances already initialized are shut down first and the original error is rethrown, with the cleanup's own error (if any) as its shutdownError property. Async factory overrides of LOGGER and DEVELOPMENT are awaited like any other. |
get | get<T>(token: Token<T>): T | Resolves a token visible from the root module, otherwise from any module that provides it, exported or not. Throws NoProviderError. |
collect | collect<T>(token: Token<T[]>): T[] | Every module's contributions to a multi token, in modules order, exported or not. |
collectEntries | collectEntries<T>(token: Token<T[]>): { value: T; injector: ModuleInjector }[] | Like collect(), with the injector of the providing module. |
modules | readonly ModuleRef[] | Every module in initialization order. |
root | ModuleRef | The root module. |
close | close(signal?: string): Promise<void> | Runs beforeApplicationShutdown(signal) then onModuleDestroy on every initialized instance, in reverse creation order. Idempotent: later calls return the same promise. During initializeLate(), no further startup hook starts once close() has begun; close() does not wait for the hook in progress (it may await close() itself), and an instance whose onModuleInit completes afterwards receives its shutdown hooks as soon as it does. Rejects with the only hook error, or an AggregateError (Application shutdown failed) for several. |
closed | boolean | true once close() has started. |
initializeLate | initializeLate(): Promise<void> | Internal to @knead-js/http: runs the startup hooks on instances created after create() (lazily built guards, middleware and filters) so they also receive shutdown hooks. Does nothing once close() has started. |
trackConstructed | trackConstructed<T>(fn: () => T): T | Internal to @knead-js/http: tracks every instance fn constructs (transients included), so the following initializeLate() gives them lifecycle hooks. Outside it, transients resolved after create() are not tracked. |
#CreateContextOptions
| Option | Type | Default | Description |
|---|---|---|---|
overrides | Iterable<readonly [Token, Provider]> | none | Replaces the provider of each token in every module that registers it. Multi providers cannot be overridden; a token no module registers is unaffected. |
globalModules | readonly ModuleImport[] | [] | Modules added to the graph before the root module. Global only if they set global: true. |
logger | Logger | new ConsoleLogger() | The default value of LOGGER. A global module that provides LOGGER takes precedence. |
development | boolean | process.env.NODE_ENV === 'development' | The default value of DEVELOPMENT. Any other value than true, false or undefined is a KneadError: The development option must be a boolean, got string. |
#Modules
#Module()
@Module(options?: ModuleOptions) is a class decorator that marks a class as a module. See
modules.
#ModuleOptions
| Field | Type | Description |
|---|---|---|
imports | ModuleImport[] | Modules whose exports this module can inject. |
providers | Provider[] | Providers owned by the module. |
controllers | Type[] | Controllers, also registered as (unexported) providers. |
exports | ModuleExport[] | Tokens and modules visible to importers. |
global | boolean | Exports are visible in every module. Default false. |
#DynamicModule
interface DynamicModule extends ModuleOptions { module: Type }. A module configured at import
time, usually returned by a static forRoot(). Its lists are appended to those of the class's
@Module() options; its global, when set, overrides the class's. Each dynamic module object is a
separate module instance. See dynamic modules.
#ModuleImport and ModuleExport
type ModuleImport = Type | DynamicModule: an entry of imports.
type ModuleExport = Token | ModuleImport: an entry of exports, either a token or an imported
module to re-export. When a module exports a token it provides itself and also re-exports a
module exporting the same token, importers see the module's own provider, whatever the order of
exports. A multi token instead collects every contribution the module exports: its own first,
then those of each re-exported module, in import order.
#The MODULES token
InjectionToken<readonly ModuleRef[]>. Every module of the application in initialization order,
as a frozen array. Injectable from every module without an import.
#ModuleRef
| Field | Type | Description |
|---|---|---|
name | string | The module class's name. |
metatype | Type | The module class. |
injector | ModuleInjector | The module's injector. |
controllers | readonly Type[] | The module's controllers. |
global | boolean | Whether the module is global. |
#ModuleInjector
The injector of one module, reachable through ModuleRef.injector and ApplicationContext.root.
It implements InjectorLike. Most applications never use it directly; these members are the ones
meant for library code:
| Member | Description |
|---|---|
name | The module's name. |
get(token, options?) | Resolves a token with this module's visibility rules: own providers, then imports' exports, then global modules' exports. Throws NoProviderError, or returns undefined with { optional: true }. |
lookup(token) | The provider records visible for token, or undefined, without creating anything. |
resolveOrConstruct(cls) | Returns cls from the module if it is a visible provider; otherwise creates it once per module in this module's injection context, honoring overrides. @knead-js/http uses it for guards, middleware and filters. |
Its other public members (records, imports, exported, state, addProvider(),
ownRecords(), resolveRecords(), instantiate(), noProvider()) are internal to the
container.
#Injection
#inject()
inject<T>(token: Token<T>): T
inject<T>(token: Token<T>, options: { optional: true }): T | undefinedReturns the value of token from the current injection context. Throws InjectionContextError
outside an injection context (synchronously inside a field initializer, constructor or provider
factory), and NoProviderError when no provider is visible, unless optional is true. See
the injection context.
#InjectOptions
{ optional?: boolean }. With optional: true, a token with no visible provider resolves to
undefined.
#runInInjectionContext()
runInInjectionContext<R>(injector: InjectorLike, fn: () => R): R. Runs fn with injector as
the current injection context, restoring the previous context afterwards, and returns fn's
result. The context covers only the synchronous part of fn.
#isInInjectionContext()
isInInjectionContext(): boolean. Whether an injection context is active.
#InjectorLike
{ get<T>(token: Token<T>, options?: InjectOptions): T | undefined }. What
runInInjectionContext() accepts. ModuleInjector implements it.
#Injectable()
@Injectable(options?: InjectableOptions). Marks a class as a provider the container may construct
and records its scope, throwing ProviderConfigError for a scope other than 'singleton' or
'transient'. Required on every class registered as providers: [Cls] or through useClass; an
unmarked one is a ProviderConfigError at bootstrap. Guard, middleware and filter classes do not
need it. Subclasses of a marked class are marked too and inherit its scope.
#markInjectable()
markInjectable(metadata: DecoratorMetadataObject): void. Marks a class as injectable from inside
another class decorator, as @Controller() and @Catch() do, keeping a scope already set by
@Injectable() or a parent class. Pass the decorator's context.metadata.
#InjectableOptions
| Option | Type | Default | Description |
|---|---|---|---|
scope | Scope | 'singleton' | The scope used when the class is a provider, unless the provider sets scope. |
#Tokens
#InjectionToken
new InjectionToken<T>(description: string). A token for a value that is not a class instance;
T is the type inject() returns. Compared by identity. description appears in error messages;
toString() returns InjectionToken(<description>).
#Token
type Token<T = unknown> = AbstractType<T> | InjectionToken<T>. Anything that identifies a
provider: a class (abstract or not) or an InjectionToken.
#Type and AbstractType
type Type<T = unknown> = new (...args: any[]) => T and
type AbstractType<T = unknown> = abstract new (...args: any[]) => T: a class and an abstract
class producing T.
#tokenName()
tokenName(token: unknown): string. The name used in error messages: an InjectionToken's
description, a class's name (<anonymous class> for an unnamed class), or String(token).
#DEVELOPMENT
InjectionToken<boolean>. Whether the application runs in development mode: the development
option of ApplicationContext.create(), by default process.env.NODE_ENV === 'development'.
KneadFactory.create() provides it from its own development option (@knead-js/http exports the
same token as HTTP_DEVELOPMENT). Injectable from every module. Packages use it to decide whether
to log full errors and unmask error responses, so it fails closed: anything other than exactly
'development' is production.
#Providers
#Provider
type Provider<T = any> = Type<T> | ClassProvider<T> | ValueProvider<T> | FactoryProvider<T> | ExistingProvider<T>An entry of a module's providers. A bare class is shorthand for { provide: C, useClass: C }.
See providers.
Every object form has provide: Token<T>, and optionally multi: true, in which case provide
is a Token<T[]> and each provider contributes one element.
| Type | Fields | Scope |
|---|---|---|
ClassProvider | useClass: Type<T>, scope?: Scope | scope, else the class's @Injectable() scope, else 'singleton' |
ValueProvider | useValue: T | Always 'singleton' |
FactoryProvider | useFactory: () => T | Promise<T>, scope?: Scope | scope, else 'singleton'. A transient factory cannot return a promise. |
ExistingProvider | useExisting: Token<T> | Always 'singleton' (an alias) |
#Scope
type Scope = 'singleton' | 'transient'. A singleton is created once per registering module and
cached; a transient provider creates a new value for every injection. Any other scope value,
in a provider or in @Injectable(), throws ProviderConfigError.
#normalizeProvider() and NormalizedProvider
normalizeProvider(provider: Provider, owner?: string): NormalizedProvider converts any provider
form to the internal shape the container uses, and throws a ProviderConfigError for a malformed
provider or an unmarked class. owner names the declaring module in that error (default
'the application'):
| { kind: 'class'; token: Token; useClass: Type; scope: Scope; multi: boolean }
| { kind: 'value'; token: Token; value: unknown; scope: 'singleton'; multi: boolean }
| { kind: 'factory'; token: Token; factory: () => unknown; scope: Scope; multi: boolean }
| { kind: 'existing'; token: Token; existing: Token; scope: 'singleton'; multi: boolean }It throws ProviderConfigError for anything that is not a class or an object with provide, for
an object without useClass, useValue, useFactory or useExisting, and for a malformed one:
provide and useExisting must be a class or an InjectionToken, useClass a class and
useFactory a function. An undefined value usually means a circular file import, and the
message says so. The message describes an
invalid object by its keys only (never its values, which may be credentials), and any other value
redacted and capped at 64 characters.
#Lifecycle hooks
Interfaces for the hook methods Knead calls on the instances it creates. See lifecycle for order and error handling.
| Interface | Method |
|---|---|
OnModuleInit | onModuleInit(): void | Promise<void> |
OnApplicationBootstrap | onApplicationBootstrap(): void | Promise<void> |
BeforeApplicationShutdown | beforeApplicationShutdown(signal?: string): void | Promise<void> |
OnModuleDestroy | onModuleDestroy(): void | Promise<void> |
#Errors
Every error extends KneadError, whose name is the subclass's name ('NoProviderError', ...).
| Class | Raised when |
|---|---|
KneadError | Base class of the errors below, and of package errors such as configuration errors. |
InjectionContextError | inject() runs outside an injection context. |
NoProviderError | No provider for the token is visible from the requesting module. The message names the requester, the module and a fix. |
CircularDependencyError | Providers depend on each other. Its path: string[] holds the cycle, for example ['A', 'B', 'A']. |
ModuleDefinitionError | The module graph is invalid: an import that is not a module, an undefined import, a circular module import, an unresolvable export. |
ProviderConfigError | A provider is malformed, a token is registered in one module both with and without multi: true, or a transient factory returns a promise. |
Messages are listed in module definition errors, missing providers and circular dependencies.
#Metadata
Typed helpers over TC39 decorator metadata (Symbol.metadata), which @knead-js/core defines when
it loads if the runtime does not. Functions taking meta are for use inside a decorator, with
context.metadata; functions taking target read from a decorated class. Values returned by the
read functions may be shared with parent classes: treat them as read-only and change metadata only
through the set and append functions. See
decorators and metadata.
#MetadataKey
new MetadataKey<T>(description: string). A typed metadata key. Each instance owns a unique
symbol, so keys never collide.
#Class metadata
| Function | Description |
|---|---|
setMetadata(meta, key, value) | Sets a value for the class. |
appendMetadata(meta, key, ...items) | Appends to a list (MetadataKey<T[]>), copying an inherited list first so the parent's is never changed. |
readMetadata(meta, key) | Reads a value inside a decorator, including one inherited from a parent class. |
getMetadata(target, key) | Reads a value from a class, including one inherited from a parent class. |
getOwnMetadata(target, key) | Reads a value the class itself set, ignoring inherited values. |
#Method metadata
| Function | Description |
|---|---|
setMethodMetadata(meta, method, key, value) | Sets a value for one method (method is context.name). |
appendMethodMetadata(meta, method, key, ...items) | Appends to a method's list without changing a parent class's list. |
readMethodMetadata(meta, method, key) | Reads a method's value inside a decorator. |
getMethodMetadata(target, method, key) | Reads a method's value from a class, including values inherited for that method. |
#INJECTABLE and MODULE_METADATA
The keys Knead's own decorators use: INJECTABLE is a MetadataKey<{ scope: Scope }> written by
@Injectable(), MODULE_METADATA a MetadataKey<ModuleOptions> written by @Module().
#Logging
The logger interface and the redaction functions that every Knead package uses. The
logging guide shows how to configure logging for an application;
@knead-js/logging provides a JSON logger.
#LOGGER and Logger
LOGGER is an InjectionToken<Logger>, injectable from every module. Its default is the
logger option of ApplicationContext.create() (a ConsoleLogger unless set); a global module
that provides LOGGER, such as LoggingModule.forRoot(), replaces it everywhere.
Logger is the interface any logger implements:
debug(message: string, ...meta: unknown[]): void
info(message: string, ...meta: unknown[]): void
warn(message: string, ...meta: unknown[]): void
error(message: string, ...meta: unknown[]): void#ConsoleLogger
new ConsoleLogger(prefix = 'knead'). Writes to console.debug, console.info, console.warn and
console.error, with the message prefixed by [prefix] . Before printing:
- the message and string
metavalues go throughredactCredentials()and thenescapeControl(), so credentials are masked and a value containing a newline cannot forge a second log line; - other
metavalues go throughredactValue().
The originals are never modified. A ConsoleLogger never throws: if formatting fails (for
example, a non-string message from untyped code), it prints the message converted with
String(), still redacted and escaped, followed by [unloggable arguments]. A message that
cannot even be converted prints as [unloggable message], and a failing console call is
ignored.
#MemoryLogger
Records every call in entries: LogEntry[], exactly as logged; clear() empties it.
@knead-js/testing uses it by default. It does not redact anything, so use it only in tests.
#DeferredLogger and WarnLogger
type WarnLogger = Pick<Logger, 'warn'>. new DeferredLogger(target, fallback) holds the warnings
of an object built outside dependency injection, such as a Redis store passed in module options,
until a logger is known:
| Member | Description |
|---|---|
warn(message) | Sends message to the current logger, or holds it while there is none. |
attach(logger) | Sends the held warnings, and every later one, to logger. Ignored once a logger is set, so an explicit target always wins. Every held warning is delivered even if logger.warn throws; the first error is then rethrown. |
use() | Called when the object is first used: with no logger attached by then, fallback() becomes the logger. |
RedisStore (@knead-js/rate-limit) and RedisLockStore (@knead-js/schedule) use it so their
construction warnings reach the application's LOGGER.
#LogEntry and LogLevel
type LogLevel = 'debug' | 'info' | 'warn' | 'error' and
interface LogEntry { level: LogLevel; message: string; meta: unknown[] }.
#logError() and ErrorLogOptions
logError(options: ErrorLogOptions, message: string, error: unknown, level: 'error' | 'warn' = 'error'): string
logs an error under a new correlation id (crypto.randomUUID()) and returns the id, which you can
show to an operator or put in an error response.
ErrorLogOptions is { readonly logger: Logger; readonly development: boolean }; pass the
injected LOGGER and DEVELOPMENT.
development | What is logged |
|---|---|
true | <message> (correlation id <id>), with the full error as meta, credentials redacted. An Error stays an Error: one with nothing to redact is passed as is, and one that needed redacting is passed as a copy whose message is redacted and whose stack is the redacted stack, causes and properties (redactValue()'s text). Other values are passed through redactValue(). |
| anything else | <message> (correlation id <id>): <summarizeError(error)>, with no meta: only Name: message, no stack, cause or properties, which tend to hold request data. |
The message is passed through redactCredentials() in both cases. A logger that throws is
ignored, so logging can never turn an error response into a second error. logError() does not
escape control characters in message; wrap client-controlled parts in logValue().
@Injectable()
export class CacheWarmer {
readonly #log = inject(LOGGER);
readonly #development = inject(DEVELOPMENT);
readonly #url = inject(REDIS_URL);
async warm(keys: string[], load: (key: string) => Promise<void>): Promise<string | undefined> {
// redactCredentials turns redis://user:pass@host into redis://***@host.
this.#log.info(`warming ${keys.length} keys from ${redactCredentials(this.#url)}`);
for (const key of keys) {
try {
await load(key);
} catch (error) {
// logError returns the correlation id it logged, to show to an operator or a client.
return logError(
{ logger: this.#log, development: this.#development },
`Warming ${logValue(key)} failed`,
error,
'warn',
);
}
}
return undefined;
}
}#redactCredentials()
redactCredentials(text: string): string. Replaces credentials in a string with ***:
- URL userinfo, for any scheme:
redis://user:pass@hostbecomesredis://***@host. The userinfo runs to the last@within the first 1024 characters after://(passwords often contain unencoded@or/). The scheme starts at the first letter of the run of scheme characters before://and keeps its last 32 characters (-redis://and1redis://are redacted too). A URL ends at whitespace, a quote or an angle bracket. Anhttp(s)URL that starts withhost[:port]/is exempt, since an@there is part of the path, but credential URLs nested in its path or query are still redacted. - Secret
key=valueandkey: valuepairs whose key is sensitive as forredactValue(): once lowercased and stripped of-and_, it containspassword,passwd,pwd,secret,token,apikey,accountkey,privatekey,connectionstring,authorization,cookie,credentialorsessionid(access_token=,DB_PASSWORD=,x-api-key=), or is exactly one of theredactValue()keys (pass,passphrase,jwt,sig,AccessKey,SharedAccessKey, ...). An unquoted value runs to the next&,;or whitespace (and, after a:, also,,}or]); a quoted value (password="hunter 2",{"password":"x"}) runs to its closing quote, backslash escapes included. Akey:with an unquoted key counts only at the start of a line or after{or,(JSON, YAML, header lines, inspected objects), so prose such asInvalid token: expiredstays readable. The value of anAuthorization:orCookie:(alsoProxy-Authorization:,Set-Cookie:) line runs to the end of the line whatever its shape; an Authorization scheme (Token ***) is kept. Keys longer than 64 characters are read only up to 64. BearerandBasiccredentials, in any case, when the credential has at least 8 token characters or is auser:pass-style pair. Shorter values, such asBearer abc123, are not redacted, so ordinary prose stays readable.
Passwords containing spaces or quotes cannot appear unencoded in a valid URL and are not recognized, nor is userinfo longer than 1024 characters. The work is linear in the input length.
#redactValue()
redactValue(value: unknown): unknown. A copy of value with credentials redacted, for logging.
The original is never modified, and a value with nothing to redact is returned as is (the same
object).
- Strings go through
redactCredentials(). - Plain objects and arrays are walked up to 4 levels deep; deeper values become
[Object]or[Array], and cycles become[Circular]. String keys are redacted too. - A key equal to
authorization,cookie,set-cookie,password,passwd,pwd,secret,token,apikey,api_key,accesstoken,refreshtoken,accountkey,privatekeyorconnectionstring(any case), or topass,passphrase,jwt,sig,accesskeyorsharedaccesskeyonce lowercased and stripped of-and_, has its value replaced by***, whatever the value (nullandundefinedstay). - A key that contains
password,passwd,pwd,secret,token,apikey,accountkey,privatekey,connectionstring,authorization,cookie,credentialorsessionidonce lowercased and stripped of-and_(db_password,x-api-key,clientSecret) has its value replaced by***too, except numbers, booleans, bigints,nullandundefined(maxTokens: 100stays). Keys longer than 128 characters are not checked for these words. - A
URLis walked as itshref, and becomes the redacted string when it held credentials (at any depth). ARequestis walked as{ method, url, headers }and aResponseas{ status, url, headers }. Headers,Mapand other objects with anentries()ortoJSON()method are walked as their entries (or as the stringtoJSON()returns), and replaced by a plain object (or that string) only when something in them was redacted.- Other class instances are walked as their own enumerable properties, like plain objects, into
a copy with the same prototype, returned only when something in them was redacted. Dates and
binary data (
ArrayBuffer, typed arrays,Buffer) are kept as they are. - Like plain objects, a class instance past the depth limit becomes
[Object](one without own properties, such as aDate, is kept) and one in a cycle becomes[Circular]. - At most 20000 strings and objects are walked per call; the rest become
[Truncated], so wide graphs of shared references stay cheap. - An
Errorwhose message, stack, cause chain or own properties (includingAggregateError.errors) need redacting becomes a string: its redacted stack, each cause as aCaused by:line (followed by the cause's own redacted properties, or, for a cause that is not an Error, the redacted cause itself), and its redacted own properties. An error with a cause chain longer than 16 is always turned into this string, ending withCaused by: … (more causes omitted). Other errors are returned unchanged. - A property whose getter throws becomes
[unreadable], and a value that defeats the walk (an exoticProxy) becomes[unloggable value: <summary>].
#summarizeError()
summarizeError(error: unknown): string. Name: message for logs outside development, with no
stack, cause or properties. The name is the error's string name, Error for an object without
one, or the typeof of a non-object. The message is redacted with redactCredentials(), then
truncated to LOGGED_MESSAGE_LENGTH characters followed by … (dropping a trailing scheme://
fragment that the cut left without its @), then both parts go through logValue().
#logValue()
logValue(value: unknown): string. Makes a client-controlled value (a path, an id, a topic) safe
to interpolate into a log message: String(value), truncated to MAX_LOGGED_VALUE_LENGTH
characters followed by …, with control characters escaped.
#escapeControl()
escapeControl(text: string): string. Escapes C0 control characters (U+0000 to U+001F) and DEL:
\n, \r and \t as those two-character sequences, the rest as \xNN.
#errorText() and isPlainObject()
Helpers the logging packages share, for loggers of your own.
errorText(error: Error): string. The error's own text, not redacted: its stack (which starts
with Name: message) when that is a string holding the message, otherwise Name: message.
isPlainObject(value: unknown): value is Record<string, unknown>. true for an object whose
prototype is Object.prototype or null (a literal or Object.create(null)), false for
everything else, including arrays, class instances and null.
#LOGGED_MESSAGE_LENGTH and MAX_LOGGED_VALUE_LENGTH
LOGGED_MESSAGE_LENGTH is 500, the longest error message summarizeError() keeps.
MAX_LOGGED_VALUE_LENGTH is 1024, the longest value logValue() keeps.
#Network helpers
Address parsing and Redis URL checks shared by the HTTP, rate-limit, GraphQL and schedule packages.
#parseIp()
parseIp(ip: string): Uint8Array | undefined. Parses an IPv4 or IPv6 address: 4 bytes for IPv4
and for IPv4-mapped IPv6 (::ffff:10.0.0.1), 16 bytes for other IPv6 addresses (an IPv6 zone such
as %eth0 is ignored), undefined for anything invalid.
#isLoopbackHostname()
isLoopbackHostname(hostname: string): boolean. Whether a URL hostname is loopback: localhost,
localhost., 127.0.0.0/8 or ::1, case-insensitive, with or without brackets, including
IPv4-mapped forms.
#plaintextRedisHost()
plaintextRedisHost(url: string | undefined): string | undefined. The host[:port] of a
redis:// or valkey:// URL whose host is not loopback, otherwise undefined: TLS URLs
(rediss://, valkeys://), Unix sockets, loopback hosts, URLs with no host (redis://),
unparseable URLs, undefined and ''.
Packages use it to warn about unencrypted connections to remote Redis servers.
#defaultRedisUrl()
defaultRedisUrl(): string | undefined. The URL Bun.redis connects to when given none:
process.env.REDIS_URL, else process.env.VALKEY_URL, else undefined (Bun then connects to
localhost). Empty values count as unset.
#sendRedisCommand()
sendRedisCommand<T>(client: RedisClient, send: (deadline: RedisDeadline) => Promise<T>, timeoutMs: number, connect = true): Promise<T>.
Runs send, a command on client, connecting the client first when connect is true and it is
disconnected (a client without the offline queue rejects sends while disconnected). Rejects with
Redis did not answer within <timeoutMs>ms when no answer arrives in time, and never starts
send after that, so a late command cannot act on an operation already reported as failed.
timeoutMs must be an integer of at least 1 (otherwise the promise rejects with a RangeError).
RedisDeadline (interface RedisDeadline { readonly expired: boolean }): deadline.expired turns true when the
timeout fires, so a send that issues a further command (RedisStore's EVAL after a
NOSCRIPT reply) checks it first. A send that ignores the argument works as before. The Redis
stores of @knead-js/rate-limit and @knead-js/schedule send their commands through it.
#ownedRedisClientOptions()
ownedRedisClientOptions(commandTimeout: number): RedisOptions. The options of a Redis client a
store builds itself: { enableOfflineQueue: false, connectionTimeout: max(commandTimeout, 1000) },
so nothing queues while Redis is unreachable. RedisStore and RedisLockStore build their own
clients with it, and connect only those clients before sending (connect of
sendRedisCommand()); a supplied client connects as its own options say.
#Durations
The duration parser behind @knead-js/schedule and @knead-js/rate-limit, for packages and
applications that take the same duration options.
#Duration
type Duration = number | `${number}${'ms' | 's' | 'm' | 'h' | 'd'}` : milliseconds, or a
string such as '500ms', '1.5s', '10m', '1h' or '1d'.
#parseDuration() and ParseDurationOptions
parseDuration(value: Duration | string, options?: ParseDurationOptions): number returns whole
milliseconds, rounded up and at least 1. It throws for a malformed string, a value that is not
positive or not finite, and anything over a year (MAX_DURATION_MS). The parse is linear in the
length of the input.
| Option | Type | Default | Description |
|---|---|---|---|
label | string | 'duration' | Names the value in error messages: Invalid lockTtl 366d: must be at most one year (365d). |
error | (message: string) => Error | a RangeError | Builds the thrown error, so a package can throw its own configuration error class. |
#MAX_DURATION_MS
31_536_000_000: the longest accepted duration, 365 days in milliseconds.