Knead

Search every guide and package page, down to section headings.

↑↓ move Enter open Esc close

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

MemberSignatureDescription
createstatic 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.
getget<T>(token: Token<T>): TResolves a token visible from the root module, otherwise from any module that provides it, exported or not. Throws NoProviderError.
collectcollect<T>(token: Token<T[]>): T[]Every module's contributions to a multi token, in modules order, exported or not.
collectEntriescollectEntries<T>(token: Token<T[]>): { value: T; injector: ModuleInjector }[]Like collect(), with the injector of the providing module.
modulesreadonly ModuleRef[]Every module in initialization order.
rootModuleRefThe root module.
closeclose(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.
closedbooleantrue once close() has started.
initializeLateinitializeLate(): 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.
trackConstructedtrackConstructed<T>(fn: () => T): TInternal 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

OptionTypeDefaultDescription
overridesIterable<readonly [Token, Provider]>noneReplaces the provider of each token in every module that registers it. Multi providers cannot be overridden; a token no module registers is unaffected.
globalModulesreadonly ModuleImport[][]Modules added to the graph before the root module. Global only if they set global: true.
loggerLoggernew ConsoleLogger()The default value of LOGGER. A global module that provides LOGGER takes precedence.
developmentbooleanprocess.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

FieldTypeDescription
importsModuleImport[]Modules whose exports this module can inject.
providersProvider[]Providers owned by the module.
controllersType[]Controllers, also registered as (unexported) providers.
exportsModuleExport[]Tokens and modules visible to importers.
globalbooleanExports 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

FieldTypeDescription
namestringThe module class's name.
metatypeTypeThe module class.
injectorModuleInjectorThe module's injector.
controllersreadonly Type[]The module's controllers.
globalbooleanWhether 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:

MemberDescription
nameThe 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()

Output
inject<T>(token: Token<T>): T
inject<T>(token: Token<T>, options: { optional: true }): T | undefined

Returns 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

OptionTypeDefaultDescription
scopeScope'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

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

TypeFieldsScope
ClassProvideruseClass: Type<T>, scope?: Scopescope, else the class's @Injectable() scope, else 'singleton'
ValueProvideruseValue: TAlways 'singleton'
FactoryProvideruseFactory: () => T | Promise<T>, scope?: Scopescope, else 'singleton'. A transient factory cannot return a promise.
ExistingProvideruseExisting: 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'):

Output
| { 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.

InterfaceMethod
OnModuleInitonModuleInit(): void | Promise<void>
OnApplicationBootstraponApplicationBootstrap(): void | Promise<void>
BeforeApplicationShutdownbeforeApplicationShutdown(signal?: string): void | Promise<void>
OnModuleDestroyonModuleDestroy(): void | Promise<void>

#Errors

Every error extends KneadError, whose name is the subclass's name ('NoProviderError', ...).

ClassRaised when
KneadErrorBase class of the errors below, and of package errors such as configuration errors.
InjectionContextErrorinject() runs outside an injection context.
NoProviderErrorNo provider for the token is visible from the requesting module. The message names the requester, the module and a fix.
CircularDependencyErrorProviders depend on each other. Its path: string[] holds the cycle, for example ['A', 'B', 'A'].
ModuleDefinitionErrorThe module graph is invalid: an import that is not a module, an undefined import, a circular module import, an unresolvable export.
ProviderConfigErrorA 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

FunctionDescription
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

FunctionDescription
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:

Output
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 meta values go through redactCredentials() and then escapeControl(), so credentials are masked and a value containing a newline cannot forge a second log line;
  • other meta values go through redactValue().

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:

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

developmentWhat 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().

cache-warmer.ts
@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@host becomes redis://***@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:// and 1redis:// are redacted too). A URL ends at whitespace, a quote or an angle bracket. An http(s) URL that starts with host[: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=value and key: value pairs whose key is sensitive as for redactValue(): once lowercased and stripped of - and _, it contains password, passwd, pwd, secret, token, apikey, accountkey, privatekey, connectionstring, authorization, cookie, credential or sessionid (access_token=, DB_PASSWORD=, x-api-key=), or is exactly one of the redactValue() 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. A key: with an unquoted key counts only at the start of a line or after { or , (JSON, YAML, header lines, inspected objects), so prose such as Invalid token: expired stays readable. The value of an Authorization: or Cookie: (also Proxy-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.
  • Bearer and Basic credentials, in any case, when the credential has at least 8 token characters or is a user:pass-style pair. Shorter values, such as Bearer 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, privatekey or connectionstring (any case), or to pass, passphrase, jwt, sig, accesskey or sharedaccesskey once lowercased and stripped of - and _, has its value replaced by ***, whatever the value (null and undefined stay).
  • A key that contains password, passwd, pwd, secret, token, apikey, accountkey, privatekey, connectionstring, authorization, cookie, credential or sessionid once lowercased and stripped of - and _ (db_password, x-api-key, clientSecret) has its value replaced by *** too, except numbers, booleans, bigints, null and undefined (maxTokens: 100 stays). Keys longer than 128 characters are not checked for these words.
  • A URL is walked as its href, and becomes the redacted string when it held credentials (at any depth). A Request is walked as { method, url, headers } and a Response as { status, url, headers }.
  • Headers, Map and other objects with an entries() or toJSON() method are walked as their entries (or as the string toJSON() 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 a Date, 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 Error whose message, stack, cause chain or own properties (including AggregateError.errors) need redacting becomes a string: its redacted stack, each cause as a Caused 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 with Caused by: … (more causes omitted). Other errors are returned unchanged.
  • A property whose getter throws becomes [unreadable], and a value that defeats the walk (an exotic Proxy) 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.

OptionTypeDefaultDescription
labelstring'duration'Names the value in error messages: Invalid lockTtl 366d: must be at most one year (365d).
error(message: string) => Errora RangeErrorBuilds 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.