injeca 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"decision-odiPb2Au.mjs","names":[],"sources":["../src/diagnostics/errors.ts","../src/primitives/decision.ts"],"sourcesContent":["import type { ServiceExplanation } from '../types/service'\n\nexport type InjecaErrorCode\n = | 'build-after-dispose'\n | 'duplicate-service'\n | 'hook-outside-build'\n | 'hook-timeout'\n | 'invalid-argument'\n | 'invalid-decision'\n | 'reentrant-dispose'\n | 'replace-while-stopping'\n | 'stale-registration'\n | 'start-timeout'\n | 'transaction-incomplete'\n | 'wait-timeout'\n\nexport interface InjecaErrorOptions {\n cause?: unknown\n}\n\n/**\n * The base class of all injeca errors. Use `code` to identify an error. The message text is not stable.\n */\nexport class InjecaError extends Error {\n readonly code: InjecaErrorCode\n\n constructor(code: InjecaErrorCode, message: string, options?: InjecaErrorOptions) {\n super(message, options)\n this.name = 'InjecaError'\n this.code = code\n }\n}\n\n/** A started container already has a current registration for the key. */\nexport class DuplicateServiceError extends InjecaError {\n declare readonly code: 'duplicate-service'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('duplicate-service', message, options)\n this.name = 'DuplicateServiceError'\n }\n}\n\n/** A registration is no longer the current registration of its service. */\nexport class StaleRegistrationError extends InjecaError {\n declare readonly code: 'stale-registration'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('stale-registration', message, options)\n this.name = 'StaleRegistrationError'\n }\n}\n\n/** A hook returned a decision from another run, or a kind that the hook does not accept. */\nexport class InvalidDecisionError extends InjecaError {\n declare readonly code: 'invalid-decision'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('invalid-decision', message, options)\n this.name = 'InvalidDecisionError'\n }\n}\n\n/** A caller used a container or an instance after its disposal started. */\nexport class BuildAfterDisposeError extends InjecaError {\n declare readonly code: 'build-after-dispose'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('build-after-dispose', message, options)\n this.name = 'BuildAfterDisposeError'\n }\n}\n\n/** `on` was called after `build` settled. */\nexport class HookOutsideBuildError extends InjecaError {\n declare readonly code: 'hook-outside-build'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('hook-outside-build', message, options)\n this.name = 'HookOutsideBuildError'\n }\n}\n\n/** A transaction commit found a declared service without `provide`. */\nexport class TransactionIncompleteError extends InjecaError {\n declare readonly code: 'transaction-incomplete'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('transaction-incomplete', message, options)\n this.name = 'TransactionIncompleteError'\n }\n}\n\n/** `dispose()` was called from the cleanup chain of the same container. */\nexport class ReentrantDisposeError extends InjecaError {\n declare readonly code: 'reentrant-dispose'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('reentrant-dispose', message, options)\n this.name = 'ReentrantDisposeError'\n }\n}\n\n/** A `start` or `stop` hook did not settle before its configured timeout. */\nexport class HookTimeoutError extends InjecaError {\n declare readonly code: 'hook-timeout'\n readonly hook: 'start' | 'stop'\n readonly timeoutMs: number\n\n constructor(message: string, hook: 'start' | 'stop', timeoutMs: number, options?: InjecaErrorOptions) {\n super('hook-timeout', message, options)\n this.name = 'HookTimeoutError'\n this.hook = hook\n this.timeoutMs = timeoutMs\n }\n}\n\n/** `replace` was called on a service in `stopping`. */\nexport class ReplaceWhileStoppingError extends InjecaError {\n declare readonly code: 'replace-while-stopping'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('replace-while-stopping', message, options)\n this.name = 'ReplaceWhileStoppingError'\n }\n}\n\n/** `start()` did not reach `running` for every service before its configured timeout. */\nexport class StartTimeoutError extends InjecaError {\n declare readonly code: 'start-timeout'\n /** One entry for each service or invocation that is not `running`. */\n readonly explanations: readonly ServiceExplanation[]\n\n constructor(message: string, explanations: readonly ServiceExplanation[], options?: InjecaErrorOptions) {\n super('start-timeout', message, options)\n this.name = 'StartTimeoutError'\n this.explanations = explanations\n }\n}\n\n/** `waitFor()` did not match before its configured timeout. */\nexport class WaitTimeoutError extends InjecaError {\n declare readonly code: 'wait-timeout'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('wait-timeout', message, options)\n this.name = 'WaitTimeoutError'\n }\n}\n\n/** A caller passed a value with the wrong shape, or reading a caller-owned property failed. */\nexport class InvalidArgumentError extends InjecaError {\n declare readonly code: 'invalid-argument'\n\n constructor(message: string, options?: InjecaErrorOptions) {\n super('invalid-argument', message, options)\n this.name = 'InvalidArgumentError'\n }\n}\n","import { InvalidDecisionError } from '../diagnostics/errors'\n\n/** The hooks that `on` registers in `build` and in an invocation callback. */\nexport type ServiceHook = 'start' | 'stop' | 'dependency-change' | 'dependency-status-change'\n\n/** The hooks that run when a dependency changes. Their context has `dependsOn` and `changes`. */\nexport type DependencyHook = 'dependency-change' | 'dependency-status-change'\n\nexport interface DecisionOptions {\n readonly reason?: string\n}\n\nexport interface ExponentialBackoff {\n readonly kind: 'exponential'\n readonly initial: number\n readonly max: number\n readonly jitter?: number\n}\n\nexport interface RequeueOptions extends DecisionOptions {\n /** A fixed delay in milliseconds. */\n readonly after?: number\n readonly backoff?: ExponentialBackoff\n readonly maxAttempts?: number\n}\n\n/** The fields that injeca fills from the hook context. */\nexport interface DecisionSource {\n /** The name of the service, or of the invocation. */\n readonly service: string\n readonly instanceId: string\n readonly hook: ServiceHook\n readonly attempt: number\n /** The time of the container clock when `decide` created the decision. */\n readonly issuedAt: number\n}\n\nexport interface HandledDecision extends DecisionSource, DecisionOptions {\n readonly kind: 'handled'\n}\n\nexport interface RebuildDecision extends DecisionSource, DecisionOptions {\n readonly kind: 'rebuild'\n}\n\nexport interface RequeueDecision extends DecisionSource, RequeueOptions {\n readonly kind: 'requeue'\n}\n\nexport interface StopDecision extends DecisionSource, DecisionOptions {\n readonly kind: 'stop'\n}\n\nexport interface FailDecision extends DecisionSource, DecisionOptions {\n readonly kind: 'fail'\n readonly error: unknown\n}\n\n/**\n * The value that a hook returns to tell injeca what to do next. Only `decide` in the hook context creates it.\n */\nexport type Decision = HandledDecision | RebuildDecision | RequeueDecision | StopDecision | FailDecision\n\nexport type DecisionKind = Decision['kind']\n\n/** The decision kinds that a hook accepts. */\nexport type HookDecisionKind<H extends ServiceHook>\n = H extends 'start'\n ? 'handled' | 'requeue' | 'stop' | 'fail'\n : H extends 'stop'\n ? 'handled' | 'fail'\n : DecisionKind\n\n/** The decisions that a hook accepts. */\nexport type DecisionFor<H extends ServiceHook> = Extract<Decision, { readonly kind: HookDecisionKind<H> }>\n\n/**\n * The value that a hook handler can return.\n *\n * A decision tells injeca what to do next. Any other value, for example the result of `app.listen()`, selects the default of the hook.\n * So the shorthand `on('start', () => app.listen(port))` type-checks.\n * The `decide` of each hook has only the methods that the hook accepts. A decision from another run fails at run time with `invalid-decision`.\n */\nexport type HookResult<_H extends ServiceHook = ServiceHook> = unknown\n\n/** All decision constructors. A hook context has only the constructors that its hook accepts. */\nexport interface Decide {\n readonly handled: (options?: DecisionOptions) => HandledDecision\n readonly rebuild: (options?: DecisionOptions) => RebuildDecision\n readonly requeue: (options?: RequeueOptions) => RequeueDecision\n readonly stop: (options?: DecisionOptions) => StopDecision\n readonly fail: (error: unknown, options?: DecisionOptions) => FailDecision\n}\n\n/** The `decide` object of a hook context. A method that the hook does not accept is a type error. */\nexport type DecideFor<H extends ServiceHook> = Pick<Decide, HookDecisionKind<H>>\n\n/**\n * One run of one hook handler. Each decision is bound to the run that created it.\n *\n * @internal\n */\nexport interface HookRun {\n readonly service: string\n readonly instanceId: string\n readonly hook: ServiceHook\n readonly attempt: number\n readonly now: () => number\n}\n\nconst issued = new WeakMap<object, HookRun>()\n\nfunction reasonFrom(options: DecisionOptions | undefined): { reason?: string } {\n return options?.reason === undefined ? {} : { reason: options.reason }\n}\n\n/** Creates the `decide` object of one run. */\nexport function createDecide(run: HookRun): Decide {\n function issue<T extends Decision>(fields: Omit<T, keyof DecisionSource>): T {\n const decision = Object.freeze({\n ...fields,\n service: run.service,\n instanceId: run.instanceId,\n hook: run.hook,\n attempt: run.attempt,\n issuedAt: run.now(),\n }) as T\n issued.set(decision, run)\n return decision\n }\n\n return Object.freeze({\n handled: (options?: DecisionOptions) => issue<HandledDecision>({ kind: 'handled', ...reasonFrom(options) }),\n rebuild: (options?: DecisionOptions) => issue<RebuildDecision>({ kind: 'rebuild', ...reasonFrom(options) }),\n requeue: (options?: RequeueOptions) => issue<RequeueDecision>({\n kind: 'requeue',\n ...reasonFrom(options),\n ...(options?.after === undefined ? {} : { after: options.after }),\n ...(options?.backoff === undefined ? {} : { backoff: Object.freeze({ ...options.backoff }) }),\n ...(options?.maxAttempts === undefined ? {} : { maxAttempts: options.maxAttempts }),\n }),\n stop: (options?: DecisionOptions) => issue<StopDecision>({ kind: 'stop', ...reasonFrom(options) }),\n fail: (error: unknown, options?: DecisionOptions) => issue<FailDecision>({ kind: 'fail', error, ...reasonFrom(options) }),\n })\n}\n\nconst validKinds: Readonly<Record<ServiceHook, ReadonlySet<DecisionKind>>> = {\n 'start': new Set(['handled', 'requeue', 'stop', 'fail']),\n 'stop': new Set(['handled', 'fail']),\n 'dependency-change': new Set(['handled', 'rebuild', 'requeue', 'stop', 'fail']),\n 'dependency-status-change': new Set(['handled', 'rebuild', 'requeue', 'stop', 'fail']),\n}\n\n/** The decision for `undefined`. A `dependency-change` handler without a decision gets the default: rebuild. */\nexport function defaultDecision(run: HookRun): Decision {\n const decide = createDecide(run)\n return run.hook === 'dependency-change' ? decide.rebuild() : decide.handled()\n}\n\n/**\n * Checks the value that a hook handler returned. It gives `undefined` for a value that `decide` did not create, so that the caller applies the default.\n * This keeps a shorthand such as `on('start', () => app.listen(port))` valid.\n *\n * @throws {InvalidDecisionError} when another run created the decision, or when the hook does not accept its kind.\n */\nexport function validateDecision(run: HookRun, value: unknown): Decision | undefined {\n const owner = typeof value === 'object' && value !== null ? issued.get(value) : undefined\n if (!owner)\n return undefined\n if (owner !== run)\n throw new InvalidDecisionError(`The '${run.hook}' hook of '${run.service}' returned a decision from another run: the '${owner.hook}' hook of '${owner.service}', instance '${owner.instanceId}', attempt ${owner.attempt}.`)\n\n const decision = value as Decision\n if (!validKinds[run.hook].has(decision.kind))\n throw new InvalidDecisionError(`The '${run.hook}' hook cannot return a '${decision.kind}' decision.`)\n\n return decision\n}\n"],"mappings":"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"}