@iskra-bun/core 0.2.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @iskra-bun/core
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e86ed55: `ErrorCode` accepts an app's own codes: declare them by merging into `ErrorCodeRegistry` (`declare module '@iskra-bun/core' { interface ErrorCodeRegistry { ORDER_LOCKED: true } }`) and use them in `IskraError`/`HttpError`. It was a closed union of Iskra's codes. New codes: `METHOD_NOT_ALLOWED`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE` and `TIMEOUT`.
8
+ - ae7c798: `traced(name, fn)` wraps a function in an OpenTelemetry span named by its layer, domain and method (`repo.usuarios.buscar`): active while it runs, so spans started inside are its children, and marked as an error on a throw or a rejection. `@opentelemetry/api` is now a dependency (without an SDK the spans are no-ops).
9
+ - ef10372: New error code `UNSUPPORTED_MEDIA_TYPE`, which web-kit answers with a 415.
10
+
3
11
  ## 0.2.0
4
12
 
5
13
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Emitter } from 'mitt';
2
2
  import pino, { Logger as Logger$1 } from 'pino';
3
+ import { Attributes, Tracer } from '@opentelemetry/api';
3
4
  import { z } from 'zod';
4
5
 
5
6
  declare const createLogger: (name: string, level?: string) => pino.Logger<never, boolean>;
@@ -227,8 +228,14 @@ declare const ErrorCodes: {
227
228
  readonly UNAUTHORIZED: "UNAUTHORIZED";
228
229
  readonly FORBIDDEN: "FORBIDDEN";
229
230
  readonly NOT_FOUND: "NOT_FOUND";
231
+ readonly METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED";
230
232
  readonly CONFLICT: "CONFLICT";
233
+ readonly PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE";
234
+ readonly UNSUPPORTED_MEDIA_TYPE: "UNSUPPORTED_MEDIA_TYPE";
231
235
  readonly VALIDATION_ERROR: "VALIDATION_ERROR";
236
+ readonly RATE_LIMITED: "RATE_LIMITED";
237
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
238
+ readonly TIMEOUT: "TIMEOUT";
232
239
  readonly DATABASE_ERROR: "DATABASE_ERROR";
233
240
  readonly CONNECTION_ERROR: "CONNECTION_ERROR";
234
241
  readonly QUERY_ERROR: "QUERY_ERROR";
@@ -242,7 +249,22 @@ declare const ErrorCodes: {
242
249
  readonly SOCKET_CONNECTION_ERROR: "SOCKET_CONNECTION_ERROR";
243
250
  readonly SOCKET_MESSAGE_ERROR: "SOCKET_MESSAGE_ERROR";
244
251
  };
245
- type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
252
+ /**
253
+ * Every error code, as keys: Iskra's own (`ErrorCodes`) and the ones an app
254
+ * adds by merging into this interface, which `ErrorCode` then accepts:
255
+ *
256
+ * ```ts
257
+ * declare module '@iskra-bun/core' {
258
+ * interface ErrorCodeRegistry {
259
+ * ORDER_LOCKED: true;
260
+ * }
261
+ * }
262
+ * throw new HttpError(423, 'The order is being edited', { code: 'ORDER_LOCKED' });
263
+ * ```
264
+ */
265
+ interface ErrorCodeRegistry extends Record<(typeof ErrorCodes)[keyof typeof ErrorCodes], true> {
266
+ }
267
+ type ErrorCode = keyof ErrorCodeRegistry & string;
246
268
  interface IskraErrorOptions {
247
269
  code: ErrorCode;
248
270
  cause?: Error;
@@ -357,6 +379,25 @@ declare function initOtel(config: OtelConfig, appName: string): Promise<void>;
357
379
  */
358
380
  declare function shutdownOtel(): Promise<void>;
359
381
 
382
+ interface TracedOptions {
383
+ /** Attributes of every span. */
384
+ attributes?: Attributes;
385
+ /** The tracer; default the global provider's `iskra` tracer (no spans without an OpenTelemetry SDK). */
386
+ tracer?: Tracer;
387
+ }
388
+ /**
389
+ * Wraps `fn` in a span named `name`, by convention `layer.domain.method`
390
+ * (`repo.usuarios.buscar`): the span is the active one while `fn` runs, so
391
+ * the spans started inside (a query, a call to another service) are its
392
+ * children; a throw or a rejection marks it as an error. Without an
393
+ * OpenTelemetry SDK it costs a no-op span.
394
+ *
395
+ * ```ts
396
+ * const buscar = traced('repo.usuarios.buscar', async (id: number) => oracle.one(SQL, { id }));
397
+ * ```
398
+ */
399
+ declare function traced<A extends unknown[], R>(name: string, fn: (...args: A) => R, options?: TracedOptions): (...args: A) => R;
400
+
360
401
  /** `NODE_ENV` as the process runs with it, or undefined when unset or empty. */
361
402
  declare function nodeEnv(): string | undefined;
362
403
  /**
@@ -871,4 +912,4 @@ declare const AppConfigSchema: z.ZodObject<{
871
912
  type AppConfigInput = z.input<typeof AppConfigSchema>;
872
913
  type AppConfigOutput = z.output<typeof AppConfigSchema>;
873
914
 
874
- export { App, type AppConfig, type AppConfigInput, type AppConfigOutput, AppConfigSchema, AppContext, type AppContextRegistry, type AppEvents, ConfigError, type Context, type Driver, DriverError, type ErrorCode, ErrorCodes, IskraError, type IskraErrorOptions, LifecycleError, type Logger, type OtelConfig, type OtelResourcesModule, type Plugin, PluginError, type ProcessConfig, type RestartBackoffConfig, SECRET_QUERY_PARAMS, autoInstrumentationOptions, createLogger, createResource, describeOtelEndpoint, initOtel, isDevelopmentEnv, isProductionEnv, loadAppConfig, nodeEnv, redactUrl, shutdownOtel };
915
+ export { App, type AppConfig, type AppConfigInput, type AppConfigOutput, AppConfigSchema, AppContext, type AppContextRegistry, type AppEvents, ConfigError, type Context, type Driver, DriverError, type ErrorCode, type ErrorCodeRegistry, ErrorCodes, IskraError, type IskraErrorOptions, LifecycleError, type Logger, type OtelConfig, type OtelResourcesModule, type Plugin, PluginError, type ProcessConfig, type RestartBackoffConfig, SECRET_QUERY_PARAMS, type TracedOptions, autoInstrumentationOptions, createLogger, createResource, describeOtelEndpoint, initOtel, isDevelopmentEnv, isProductionEnv, loadAppConfig, nodeEnv, redactUrl, shutdownOtel, traced };
package/dist/index.js CHANGED
@@ -247,8 +247,14 @@ var ErrorCodes = {
247
247
  UNAUTHORIZED: "UNAUTHORIZED",
248
248
  FORBIDDEN: "FORBIDDEN",
249
249
  NOT_FOUND: "NOT_FOUND",
250
+ METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED",
250
251
  CONFLICT: "CONFLICT",
252
+ PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
253
+ UNSUPPORTED_MEDIA_TYPE: "UNSUPPORTED_MEDIA_TYPE",
251
254
  VALIDATION_ERROR: "VALIDATION_ERROR",
255
+ RATE_LIMITED: "RATE_LIMITED",
256
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
257
+ TIMEOUT: "TIMEOUT",
252
258
  // Data
253
259
  DATABASE_ERROR: "DATABASE_ERROR",
254
260
  CONNECTION_ERROR: "CONNECTION_ERROR",
@@ -730,6 +736,43 @@ var App = class {
730
736
  this.signals = [];
731
737
  }
732
738
  };
739
+
740
+ // src/traced.ts
741
+ import { SpanStatusCode, trace } from "@opentelemetry/api";
742
+ function fail(span, error) {
743
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
744
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.name : "Error" });
745
+ }
746
+ function traced(name, fn, options = {}) {
747
+ return function(...args) {
748
+ const tracer = options.tracer ?? trace.getTracer("iskra");
749
+ return tracer.startActiveSpan(name, { attributes: options.attributes }, (span) => {
750
+ let result;
751
+ try {
752
+ result = fn.apply(this, args);
753
+ } catch (error) {
754
+ fail(span, error);
755
+ span.end();
756
+ throw error;
757
+ }
758
+ if (result instanceof Promise) {
759
+ return result.then(
760
+ (value) => {
761
+ span.end();
762
+ return value;
763
+ },
764
+ (error) => {
765
+ fail(span, error);
766
+ span.end();
767
+ throw error;
768
+ }
769
+ );
770
+ }
771
+ span.end();
772
+ return result;
773
+ });
774
+ };
775
+ }
733
776
  export {
734
777
  App,
735
778
  AppConfigSchema,
@@ -751,6 +794,7 @@ export {
751
794
  loadAppConfig,
752
795
  nodeEnv,
753
796
  redactUrl,
754
- shutdownOtel
797
+ shutdownOtel,
798
+ traced
755
799
  };
756
800
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/app.ts","../src/logger/index.ts","../src/env.ts","../src/config/loader.ts","../src/config/schema.ts","../src/errors.ts","../src/otel.ts"],"sourcesContent":["import mitt, { type Emitter } from 'mitt';\nimport { createLogger, type Logger } from './logger';\nimport { loadAppConfig } from './config/loader';\nimport type { AppConfig, AppContextRegistry, AppEvents, Driver, Plugin, Context } from './types';\nimport { LifecycleError } from './errors';\nimport { describeOtelEndpoint, initOtel, shutdownOtel } from './otel';\nimport { nodeEnv } from './env';\n\n/**\n * `app.context`: a Map whose keys in AppContextRegistry (`'db'`, `'kv'`, …)\n * are typed; any other key holds `unknown`, or the type given as `T`.\n */\nexport class AppContext extends Map<string, unknown> {\n override get<K extends keyof AppContextRegistry>(key: K): AppContextRegistry[K] | undefined;\n override get<T = unknown>(key: string): T | undefined;\n override get(key: string): unknown {\n return super.get(key);\n }\n\n override set<K extends keyof AppContextRegistry>(key: K, value: AppContextRegistry[K]): this;\n override set(key: string, value: unknown): this;\n override set(key: string, value: unknown): this {\n return super.set(key, value);\n }\n}\n\nconst DEFAULT_SHUTDOWN_SIGNALS: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;\n\nexport class App {\n public config: AppConfig;\n public logger: Logger;\n public events: Emitter<Record<string, unknown>>;\n private drivers: Driver[] = [];\n /** Drivers whose start() succeeded, in start order; null until start() runs. */\n private startedDrivers: Driver[] | null = null;\n private pendingInstalls: PromiseLike<void>[] = [];\n /** The shutdown in progress, shared by concurrent stop() calls. */\n private stopping: Promise<void> | null = null;\n private signalHandler?: (signal: NodeJS.Signals) => void;\n private signals: NodeJS.Signals[] = [];\n public context = new AppContext();\n\n constructor(config?: AppConfig) {\n this.events = mitt();\n // Temporary config until init() is called (or passed in constructor)\n this.config = config || ({ name: 'Bootstrapping', logger: { level: 'info' } } as AppConfig);\n this.logger = createLogger(this.config.name, this.config.logger?.level);\n }\n\n async init() {\n // If config was not passed, load it\n if (this.config.name === 'Bootstrapping') {\n this.config = await loadAppConfig();\n this.logger = createLogger(this.config.name, this.config.logger?.level);\n }\n\n // Initialize OpenTelemetry SDK before drivers (so auto-instrumentation patches libraries)\n if (this.config.otel && this.config.otel.enabled !== false) {\n await initOtel(this.config.otel, this.config.name);\n // Its origin only: the endpoint's path, query or userinfo can hold an API key.\n const { origin, plaintext } = describeOtelEndpoint(this.config.otel);\n this.logger.info({ endpoint: origin }, 'OpenTelemetry initialized');\n if (plaintext) {\n this.logger.warn(\n { endpoint: origin },\n 'OpenTelemetry endpoint is plain http:// on a remote host: spans (URLs, captured headers) travel unencrypted; use https://',\n );\n }\n }\n\n this.logger.info('Initializing App...');\n\n // Async plugin installs are awaited here so a failure surfaces from\n // start() instead of crashing the process as an unhandled rejection.\n await Promise.all(this.pendingInstalls);\n this.pendingInstalls = [];\n\n // Only the drivers whose init() ran are rolled back: one without an\n // init hook was never initialized and has nothing to release yet.\n const initialized: Driver[] = [];\n for (const driver of this.drivers) {\n if (!driver.init) continue;\n this.logger.debug(`Initializing driver: ${driver.name}`);\n initialized.push(driver);\n try {\n await driver.init(this);\n } catch (err) {\n // The failing driver may hold part of what it opened (a pool, a\n // child process), so it is stopped too, with the ones before it.\n this.logger.error(\n { err, driver: driver.name },\n 'Driver failed to initialize; stopping the drivers already initialized',\n );\n await this.rollback(initialized.reverse());\n throw err;\n }\n }\n }\n\n register(driver: Driver) {\n this.drivers.push(driver);\n return this;\n }\n\n use(plugin: Plugin) {\n const result = plugin.install(this);\n if (result && typeof (result as PromiseLike<void>).then === 'function') {\n const install = Promise.resolve(result);\n // Observed now so an early rejection is not reported as unhandled;\n // init() awaits the original promise and rethrows its error.\n install.catch(() => {});\n this.pendingInstalls.push(install);\n }\n return this;\n }\n\n /**\n * Runs `handler` for every `event`. Events in AppEvents give the handler a\n * typed payload; for others it is `unknown`, or the type given as `T`.\n */\n on<K extends keyof AppEvents>(event: K, handler: (ctx: Context<AppEvents[K]>) => Promise<void> | void): this;\n on<T = unknown>(event: string, handler: (ctx: Context<T>) => Promise<void> | void): this;\n on(event: string, handler: (ctx: Context<unknown>) => Promise<void> | void): this {\n this.events.on(event, async (payload) => {\n const ctx: Context<unknown> = {\n app: this,\n logger: this.logger.child({ event }),\n payload,\n reply: (data) => this.events.emit(`${event}:reply`, data),\n };\n\n try {\n await handler(ctx);\n } catch (err) {\n this.logger.error({ err, event }, 'Error handling event');\n }\n });\n return this;\n }\n\n /** Emits `event`; for events in AppEvents the payload must match their type. */\n emit<K extends keyof AppEvents>(event: K, payload: AppEvents[K]): void;\n emit(event: string, payload?: unknown): void;\n emit(event: string, payload?: unknown): void {\n this.events.emit(event, payload);\n }\n\n /**\n * Starts drivers one at a time, in registration order, so a driver can rely\n * on the ones registered before it (e.g. the DB before the web server). If\n * one fails, every driver is stopped in reverse order (all were initialized\n * and may hold connections, including the failing one), OpenTelemetry is\n * shut down and the error is rethrown, instead of leaving ports bound and\n * children running.\n */\n async start() {\n await this.init();\n if (!nodeEnv()) {\n this.logger.warn(\n 'NODE_ENV is not set: production defaults apply (Secure cookies, no sample secrets, JSON logs). ' +\n 'Set NODE_ENV=development for local development.',\n );\n }\n this.logger.info('Starting drivers...');\n const started: Driver[] = [];\n for (const driver of this.drivers) {\n try {\n if (driver.start) await driver.start();\n started.push(driver);\n } catch (err) {\n this.logger.error({ err, driver: driver.name }, 'Driver failed to start; stopping every driver');\n await this.rollback([...this.drivers].reverse());\n throw err;\n }\n }\n this.startedDrivers = started;\n this.installSignalHandlers();\n this.logger.info('App started successfully 🚀');\n }\n\n /**\n * Stops drivers in reverse start order (the web server before the DB it\n * uses), continuing past failures, and always flushes OpenTelemetry.\n *\n * A call made while a stop is in progress (e.g. an app's own signal\n * handler next to the App's) gets that same stop: it resolves only once\n * every driver has stopped, instead of right away with nothing left to do.\n */\n stop(): Promise<void> {\n this.stopping ??= this.stopOnce().finally(() => {\n this.stopping = null;\n });\n return this.stopping;\n }\n\n private async stopOnce() {\n this.logger.info('Stopping app...');\n const toStop = [...(this.startedDrivers ?? this.drivers)].reverse();\n this.startedDrivers = [];\n\n let failures: PromiseRejectedResult[];\n try {\n failures = await this.stopDrivers(toStop);\n } finally {\n // Shutdown OTel SDK after drivers (flush pending spans/metrics)\n await shutdownOtel();\n // Removed only now: while the drivers stop, a second signal must\n // still reach the handler (forced exit) instead of the default action.\n this.removeSignalHandlers();\n }\n\n if (failures.length > 0) {\n this.logger.error({ failures }, 'Some drivers failed to stop');\n throw new LifecycleError('Some drivers failed to stop', {\n failures,\n context: { driverCount: toStop.length, failedCount: failures.length },\n });\n }\n\n this.logger.info('App stopped.');\n }\n\n /**\n * Stops `drivers` after a failed init() or start() and shuts down\n * OpenTelemetry. Failures to stop are logged, not thrown: the caller\n * rethrows the original error. A later stop() stops nothing more.\n */\n private async rollback(drivers: Driver[]) {\n this.startedDrivers = [];\n await this.stopDrivers(drivers);\n try {\n await shutdownOtel();\n } catch (err) {\n this.logger.error({ err }, 'OpenTelemetry failed to shut down');\n }\n }\n\n private async stopDrivers(drivers: Driver[]): Promise<PromiseRejectedResult[]> {\n const failures: PromiseRejectedResult[] = [];\n for (const driver of drivers) {\n try {\n if (driver.stop) await driver.stop();\n } catch (reason) {\n this.logger.error({ err: reason, driver: driver.name }, 'Driver failed to stop');\n failures.push({ status: 'rejected', reason });\n }\n }\n return failures;\n }\n\n /**\n * Stops the app gracefully on SIGTERM/SIGINT (configurable with\n * `shutdownSignals`, off by default under NODE_ENV=test). Once a listener\n * exists the runtime no longer exits on its own, so the handler exits with\n * 0 after a clean stop, or 1 on failure / after `shutdownTimeoutMs`. A\n * second signal forces exit.\n */\n private installSignalHandlers() {\n const configured = this.config.shutdownSignals;\n const signals =\n configured === false ? [] : (configured ?? (nodeEnv() === 'test' ? [] : DEFAULT_SHUTDOWN_SIGNALS));\n if (signals.length === 0 || this.signalHandler) return;\n\n const timeoutMs = this.config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n let shuttingDown = false;\n this.signalHandler = (signal) => {\n if (shuttingDown) {\n this.logger.warn({ signal }, 'Second shutdown signal; exiting immediately');\n process.exit(1);\n }\n shuttingDown = true;\n this.logger.info({ signal }, 'Shutdown signal received; stopping app');\n const timer = setTimeout(() => {\n this.logger.error({ timeoutMs }, 'Graceful shutdown timed out; exiting');\n process.exit(1);\n }, timeoutMs);\n this.stop().then(\n () => {\n clearTimeout(timer);\n process.exit(0);\n },\n (err) => {\n clearTimeout(timer);\n this.logger.error({ err }, 'Graceful shutdown failed');\n process.exit(1);\n },\n );\n };\n this.signals = signals as NodeJS.Signals[];\n for (const signal of this.signals) process.on(signal, this.signalHandler);\n }\n\n private removeSignalHandlers() {\n if (!this.signalHandler) return;\n for (const signal of this.signals) process.off(signal, this.signalHandler);\n this.signalHandler = undefined;\n this.signals = [];\n }\n}\n","import pino from 'pino';\nimport pretty from 'pino-pretty';\nimport { isDevelopmentEnv } from '../env';\n\n/**\n * Field names whose values are replaced with `[REDACTED]`, at any depth. Keys\n * are compared lowercased and without `-` or `_`, so `apiKey`, `api_key` and\n * `X-API-Key` are the same key.\n */\nconst REDACTED_KEYS = [\n 'password',\n 'pass',\n 'passwd',\n 'apiKey',\n 'apiSecret',\n 'token',\n 'authToken',\n 'accessToken',\n 'refreshToken',\n 'idToken',\n 'secret',\n 'clientSecret',\n 'secretKey',\n 'privateKey',\n 'authorization',\n 'proxyAuthorization',\n 'cookie',\n 'setCookie',\n 'sessionId',\n] as const;\n\n/**\n * Keys ending in one of these are redacted too (normalized the same way):\n * `dbPassword`, `x-api-key`, `x-auth-token`, `AWS_SECRET_ACCESS_KEY`,\n * `webhookSecret`.\n */\nconst REDACTED_SUFFIXES = ['password', 'passwd', 'secret', 'token', 'apikey', 'secretkey', 'privatekey', 'accesskey'];\n\nconst normalizeKey = (key: string): string => key.toLowerCase().replace(/[-_]/g, '');\nconst SENSITIVE = new Set<string>(REDACTED_KEYS.map(normalizeKey));\nconst isSensitive = (key: string): boolean => {\n const normalized = normalizeKey(key);\n return SENSITIVE.has(normalized) || REDACTED_SUFFIXES.some((suffix) => normalized.endsWith(suffix));\n};\nconst CENSOR = '[REDACTED]';\n/** Objects deeper than this are logged as they are. */\nconst MAX_DEPTH = 8;\nconst IN_PROGRESS = Symbol('in progress');\n\n/** The prototype of what pino's err serializer returns (an error's fields, causes included). */\nconst SERIALIZED_ERROR_PROTO = Object.getPrototypeOf(pino.stdSerializers.err(new Error()));\n\nconst isPlainObject = (value: object): boolean => {\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null || proto === SERIALIZED_ERROR_PROTO;\n};\n\n/** Query parameter names whose values are masked in messages. */\nconst SECRET_PARAM = /pass|secret|token|key|sig|auth/i;\n\n/**\n * Passwords in `scheme://user:password@host` and secret-looking query\n * parameters (`?authToken=`, `&X-Amz-Signature=`) of a message: drivers put\n * the connection string in the errors they throw. Bounded quantifiers keep\n * both patterns linear on long input.\n */\nfunction maskCredentials(text: string): string {\n return text\n .replace(/\\b([a-z][\\w+.-]{0,30}:\\/\\/[^\\s:@/]{0,256}):[^\\s@/]{1,256}@/gi, '$1:[REDACTED]@')\n .replace(/([?&])([\\w.-]{1,100})=([^&\\s'\"#]+)/g, (match, sep: string, name: string) =>\n SECRET_PARAM.test(name) ? `${sep}${name}=[REDACTED]` : match,\n );\n}\n\n/**\n * An Error as pino's err serializer writes it (type, message and stack with\n * their causes, and its own fields), scrubbed. pino serializes errors after\n * the log formatter runs, so an HTTP client's error (`config.headers\n * .Authorization`) or a Redis error (`command.args` of AUTH) went out as is.\n */\nfunction scrubError(err: Error, depth: number, done: WeakMap<object, unknown>): unknown {\n const serialized = { ...pino.stdSerializers.err(err) } as Record<string, unknown>;\n if (typeof serialized.message === 'string') serialized.message = maskCredentials(serialized.message);\n if (typeof serialized.stack === 'string') serialized.stack = maskCredentials(serialized.stack);\n // Deep: an error keeps the client's objects (axios's `request`, whose\n // `_options.headers` hold the Authorization header), which are class\n // instances that JSON.stringify writes out in full.\n return scrubEntries(serialized, depth, done, true);\n}\n\n/**\n * Replaces the value of every sensitive key, at any depth, in a copy of the\n * objects that contain one (the caller's objects are not modified). pino's\n * own `redact` has no recursive wildcard, and listing each key at every depth\n * made logging 14 times slower. Errors are serialized and scrubbed here (see\n * scrubError), and objects with a toJSON() are scrubbed as what it returns;\n * other class instances are left to pino, except inside an error.\n */\nfunction scrub(value: unknown, depth: number, done: WeakMap<object, unknown>, deep = false): unknown {\n if (value === null || typeof value !== 'object' || depth > MAX_DEPTH) return value;\n if (done.has(value)) {\n const result = done.get(value);\n // Still being walked: a cycle, which pino would print as [Circular].\n return result === IN_PROGRESS ? '[Circular]' : result;\n }\n const isError = value instanceof Error;\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n const walk = isError || Array.isArray(value) || isPlainObject(value) || typeof toJSON === 'function' || deep;\n if (!walk) return value;\n done.set(value, IN_PROGRESS);\n let result: unknown;\n if (isError) {\n result = scrubError(value, depth, done);\n } else if (Array.isArray(value)) {\n let copy: unknown[] | undefined;\n value.forEach((item, i) => {\n const scrubbed = scrub(item, depth + 1, done, deep);\n if (scrubbed !== item) (copy ??= value.slice())[i] = scrubbed;\n });\n result = copy ?? value;\n } else if (isPlainObject(value)) {\n result = scrubEntries(value as Record<string, unknown>, depth, done, deep);\n } else if (typeof toJSON === 'function') {\n // Written as its toJSON(), which is what JSON.stringify writes: an axios\n // error's `config.headers` is an AxiosHeaders instance whose\n // Authorization header went out as is.\n result = scrub(toJSON.call(value), depth, done, deep);\n } else {\n // Inside an error: another class instance, as JSON.stringify writes it\n // (its own enumerable fields).\n result = scrubEntries({ ...(value as Record<string, unknown>) }, depth, done, deep);\n }\n done.set(value, result);\n return result;\n}\n\n/** An object with its sensitive keys censored and its values scrubbed; a copy if anything changed. */\nfunction scrubEntries(\n value: Record<string, unknown>,\n depth: number,\n done: WeakMap<object, unknown>,\n deep = false,\n): Record<string, unknown> {\n let copy: Record<string, unknown> | undefined;\n for (const [key, item] of Object.entries(value)) {\n const scrubbed = isSensitive(key) ? CENSOR : scrub(item, depth + 1, done, deep);\n if (scrubbed !== item) (copy ??= { ...value })[key] = scrubbed;\n }\n // A nested serialized error (pino's prototype) becomes a plain object either way.\n return copy ?? (Object.getPrototypeOf(value) === SERIALIZED_ERROR_PROTO ? { ...value } : value);\n}\n\nexport const createLogger = (name: string, level: string = 'info') => {\n // Pretty output only in development/test: JSON otherwise, NODE_ENV unset included.\n const isDev = isDevelopmentEnv();\n const options: pino.LoggerOptions = {\n name,\n level,\n formatters: {\n log: (object) => scrub(object, 0, new WeakMap()) as Record<string, unknown>,\n },\n // The formatter above already serialized errors (scrubbed): pino's own\n // err serializer would take that plain object for an error again and\n // rewrite its `type` as \"Object\".\n serializers: {\n err: (value: unknown) => (value instanceof Error ? scrub(value, 0, new WeakMap()) : value),\n },\n hooks: {\n // Messages are strings the formatter never sees: mask connection\n // string passwords there too, including the message pino takes\n // from an error logged on its own (`logger.error(err)`).\n logMethod(args, method) {\n const masked: unknown[] = args.map((arg) => (typeof arg === 'string' ? maskCredentials(arg) : arg));\n if (masked[0] instanceof Error && typeof masked[1] !== 'string') {\n masked.splice(1, 0, maskCredentials(masked[0].message));\n }\n return method.apply(this, masked as Parameters<typeof method>);\n },\n },\n redact: {\n paths: ['config.env', '*.data'],\n censor: CENSOR,\n },\n };\n // pino-pretty as an in-process stream, not a `transport`: a transport runs\n // in a worker thread that loads the module by name at runtime, which fails\n // in a `bun build --compile` binary and crashed it at startup.\n const logger = isDev ? pino(options, pretty({ colorize: true })) : pino(options);\n\n // Bindings (`logger.child({ ... })`) do not go through formatters.log:\n // scrub them here. A child's own children inherit this child().\n type Child = (this: pino.Logger, bindings: pino.Bindings, options?: object) => pino.Logger;\n const child = logger.child as unknown as Child;\n logger.child = function (this: pino.Logger, bindings: pino.Bindings, childOptions?: object) {\n return child.call(this, scrub(bindings, 0, new WeakMap()) as pino.Bindings, childOptions);\n } as unknown as typeof logger.child;\n return logger;\n};\n\nexport type Logger = pino.Logger;\n","/**\n * The deployment environment, read when called: `NODE_ENV` through a computed\n * key, because `bun build` replaces a literal `process.env.NODE_ENV` with its\n * value at build time (\"development\" when unset), so a compiled binary ignored\n * the NODE_ENV it ran with.\n */\nconst NODE_ENV = 'NODE_ENV';\n\n/** `NODE_ENV` as the process runs with it, or undefined when unset or empty. */\nexport function nodeEnv(): string | undefined {\n return process.env[NODE_ENV] || undefined;\n}\n\n/**\n * Whether development conveniences apply (pretty logs, http auth URLs, sample\n * secrets, `disableCSRFCheck`): only with `NODE_ENV` set to `development` or\n * `test`.\n */\nexport function isDevelopmentEnv(env: string | undefined = nodeEnv()): boolean {\n return env === 'development' || env === 'test';\n}\n\n/**\n * Whether production safeguards apply: in every environment but `development`\n * and `test`, including an unset NODE_ENV and names like `staging`. They used\n * to apply only to NODE_ENV=production exactly, so a deploy that forgot it\n * sent non-Secure cookies and accepted placeholder secrets.\n */\nexport function isProductionEnv(env: string | undefined = nodeEnv()): boolean {\n return !isDevelopmentEnv(env);\n}\n","import { loadConfig } from 'c12';\nimport { AppConfigSchema } from './schema';\nimport type { AppConfig } from '../types';\n\nexport async function loadAppConfig(cwd: string = process.cwd()): Promise<AppConfig> {\n const { config } = await loadConfig<AppConfig>({\n name: 'app',\n configFile: 'app.config',\n cwd,\n dotenv: true,\n // c12 would also merge a `.apprc` file from the working directory (a\n // plain key=value file that could add `processes` to spawn or move\n // `otel.endpoint`), and download and run `extends` layers from\n // github:/gitlab:/https:// sources on every start. Local `extends`\n // paths still work.\n rcFile: false,\n giget: false,\n });\n\n const validConfig = AppConfigSchema.parse(config || {});\n return validConfig as AppConfig;\n}\n","import { z } from 'zod';\n\nconst RestartBackoffSchema = z.object({\n initialMs: z.number().positive().optional(),\n maxMs: z.number().positive().optional(),\n factor: z.number().positive().optional(),\n});\n\n// Sections owned by the kits are validated here for shape only, and every\n// object is `.passthrough()`: AppConfig promises `[key: string]: any`, and a\n// plain z.object() silently strips unknown keys — which used to drop `db`,\n// `kv` and `socket` from app.config.ts entirely.\nexport const AppConfigSchema = z\n .object({\n name: z.string().default('IskraApp'),\n debug: z.boolean().default(false),\n logger: z\n .object({\n level: z.string().default('info'),\n })\n .passthrough()\n .default({}),\n otel: z\n .object({\n enabled: z.boolean().default(true),\n endpoint: z.string().default('http://localhost:4318'),\n serviceName: z.string().optional(),\n serviceVersion: z.string().default('0.1.0'),\n environment: z.string().optional(),\n metricIntervalMs: z.number().default(60_000),\n resourceAttributes: z.record(z.string()).optional(),\n // passthrough: the instrumentations' own options (hooks, redactedQueryParams) were stripped.\n instrumentations: z.record(z.object({ enabled: z.boolean().optional() }).passthrough()).optional(),\n })\n .passthrough()\n .optional(),\n shutdownSignals: z.union([z.array(z.string()), z.literal(false)]).optional(),\n shutdownTimeoutMs: z.number().positive().optional(),\n processes: z\n .record(\n z\n .object({\n command: z.string(),\n args: z.array(z.string()).optional(),\n mode: z.enum(['daemon', 'oneshot', 'stdio']).default('daemon'),\n restartOnCrash: z.boolean().default(false),\n maxRestarts: z.number().int().nonnegative().optional(),\n restartCooldown: z.number().nonnegative().optional(),\n restartBackoff: RestartBackoffSchema.optional(),\n env: z.record(z.string()).optional(),\n inheritEnv: z.union([z.boolean(), z.array(z.string())]).optional(),\n maxPendingStdinBytes: z.number().int().positive().optional(),\n })\n .passthrough(),\n )\n .optional(),\n socket: z\n .object({\n enabled: z.boolean(),\n port: z.number().optional(),\n adapter: z.enum(['bun', 'socket.io']).optional(),\n })\n .passthrough()\n .optional(),\n kv: z\n .object({\n driver: z.enum(['memory', 'redis']),\n connection: z.any().optional(),\n })\n .passthrough()\n .optional(),\n db: z\n .object({\n driver: z.enum(['postgres', 'mysql', 'sqlite', 'libsql']),\n url: z.string(),\n authToken: z.string().optional(),\n })\n .passthrough()\n .optional(),\n })\n .passthrough();\n\nexport type AppConfigInput = z.input<typeof AppConfigSchema>;\nexport type AppConfigOutput = z.output<typeof AppConfigSchema>;\n","export const ErrorCodes = {\n // General\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n NOT_INITIALIZED: 'NOT_INITIALIZED',\n ADAPTER_ERROR: 'ADAPTER_ERROR',\n\n // Config\n CONFIG_INVALID: 'CONFIG_INVALID',\n CONFIG_MISSING: 'CONFIG_MISSING',\n\n // Driver\n DRIVER_INIT_FAILED: 'DRIVER_INIT_FAILED',\n DRIVER_START_FAILED: 'DRIVER_START_FAILED',\n DRIVER_STOP_FAILED: 'DRIVER_STOP_FAILED',\n DRIVER_NOT_FOUND: 'DRIVER_NOT_FOUND',\n\n // Plugin\n PLUGIN_INSTALL_FAILED: 'PLUGIN_INSTALL_FAILED',\n\n // Lifecycle\n LIFECYCLE_START_FAILED: 'LIFECYCLE_START_FAILED',\n LIFECYCLE_STOP_FAILED: 'LIFECYCLE_STOP_FAILED',\n\n // HTTP\n BAD_REQUEST: 'BAD_REQUEST',\n UNAUTHORIZED: 'UNAUTHORIZED',\n FORBIDDEN: 'FORBIDDEN',\n NOT_FOUND: 'NOT_FOUND',\n CONFLICT: 'CONFLICT',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n\n // Data\n DATABASE_ERROR: 'DATABASE_ERROR',\n CONNECTION_ERROR: 'CONNECTION_ERROR',\n QUERY_ERROR: 'QUERY_ERROR',\n MIGRATION_ERROR: 'MIGRATION_ERROR',\n CACHE_ERROR: 'CACHE_ERROR',\n\n // Storage / Upload\n STORAGE_ERROR: 'STORAGE_ERROR',\n FILE_UPLOAD_ERROR: 'FILE_UPLOAD_ERROR',\n FILE_NOT_FOUND: 'FILE_NOT_FOUND',\n\n // Worker\n QUEUE_ERROR: 'QUEUE_ERROR',\n JOB_ERROR: 'JOB_ERROR',\n\n // Socket\n SOCKET_CONNECTION_ERROR: 'SOCKET_CONNECTION_ERROR',\n SOCKET_MESSAGE_ERROR: 'SOCKET_MESSAGE_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\nexport interface IskraErrorOptions {\n code: ErrorCode;\n cause?: Error;\n context?: Record<string, unknown>;\n}\n\n/**\n * Clase base para todos los errores del framework Iskra.\n * Soporta encadenamiento de errores vía `cause` y metadata arbitraria en `context`.\n */\nexport class IskraError extends Error {\n public readonly code: ErrorCode;\n public readonly context: Record<string, unknown>;\n\n constructor(message: string, options: IskraErrorOptions) {\n super(message, { cause: options.cause });\n this.name = 'IskraError';\n this.code = options.code;\n this.context = options.context ?? {};\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n context: this.context,\n ...(this.cause instanceof Error ? { cause: this.cause.message } : {}),\n };\n }\n}\n\n// ─── Config Errors ───────────────────────────────────────────────────────────\n\nexport class ConfigError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.CONFIG_INVALID, ...options });\n this.name = 'ConfigError';\n }\n}\n\n// ─── Driver Errors ───────────────────────────────────────────────────────────\n\nexport class DriverError extends IskraError {\n constructor(message: string, options?: { code?: ErrorCode; cause?: Error; context?: Record<string, unknown> }) {\n super(message, {\n code: options?.code ?? ErrorCodes.DRIVER_INIT_FAILED,\n cause: options?.cause,\n context: options?.context,\n });\n this.name = 'DriverError';\n }\n}\n\n// ─── Plugin Errors ───────────────────────────────────────────────────────────\n\nexport class PluginError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.PLUGIN_INSTALL_FAILED, ...options });\n this.name = 'PluginError';\n }\n}\n\n// ─── Lifecycle Errors ────────────────────────────────────────────────────────\n\nexport class LifecycleError extends IskraError {\n public readonly failures: PromiseRejectedResult[];\n\n constructor(\n message: string,\n options: { failures?: PromiseRejectedResult[]; cause?: Error; context?: Record<string, unknown> },\n ) {\n super(message, { code: ErrorCodes.LIFECYCLE_STOP_FAILED, cause: options.cause, context: options.context });\n this.name = 'LifecycleError';\n this.failures = options.failures ?? [];\n }\n}\n","/**\n * OpenTelemetry SDK initialization for Iskra apps.\n *\n * All OTel packages are dynamic-imported so they remain optional peer deps.\n * Apps that don't set `config.otel` never load this code.\n *\n * The module specifiers are held in variables rather than passed as string\n * literals to `import()`. This stops `tsc` from statically resolving the\n * optional `@opentelemetry/*` packages at compile time, so the module\n * typechecks cleanly even when those peer deps are absent. At runtime the\n * imports still resolve normally, and a missing package degrades to a clear,\n * actionable error instead of an unhandled module-resolution failure.\n */\nimport type { OtelConfig } from './types';\nimport { nodeEnv } from './env';\n\nlet sdkInstance: { shutdown(): Promise<void> } | null = null;\n\n/** Optional OTel package specifiers, indirected so tsc does not resolve them statically. */\nconst OTEL_MODULES = {\n sdkNode: '@opentelemetry/sdk-node',\n autoInstrumentations: '@opentelemetry/auto-instrumentations-node',\n traceExporter: '@opentelemetry/exporter-trace-otlp-http',\n metricExporter: '@opentelemetry/exporter-metrics-otlp-http',\n sdkMetrics: '@opentelemetry/sdk-metrics',\n resources: '@opentelemetry/resources',\n semconv: '@opentelemetry/semantic-conventions',\n} as const;\n\n// The optional OTel packages are loaded by specifier, untyped; these are the\n// parts of them used here.\ntype Ctor<T = unknown> = new (options: Record<string, unknown>) => T;\ninterface OtelSdkNode {\n NodeSDK: Ctor<{ start(): void; shutdown(): Promise<void> }>;\n}\ninterface OtelAutoInstrumentations {\n getNodeAutoInstrumentations(overrides: Record<string, unknown>): unknown;\n}\ninterface OtelTraceExporter {\n OTLPTraceExporter: Ctor;\n}\ninterface OtelMetricExporter {\n OTLPMetricExporter: Ctor;\n}\ninterface OtelSdkMetrics {\n PeriodicExportingMetricReader: Ctor;\n}\ninterface OtelSemconv {\n ATTR_SERVICE_NAME?: string;\n ATTR_SERVICE_VERSION?: string;\n}\n/** `@opentelemetry/resources`: 2.x exports resourceFromAttributes(), 1.x the Resource class. */\nexport interface OtelResourcesModule {\n resourceFromAttributes?: (attributes: Record<string, string>) => unknown;\n Resource?: new (attributes: Record<string, string>) => unknown;\n}\n\n/** Import an optional module by specifier; the indirection keeps tsc from resolving it. */\nfunction importOptional<T>(specifier: string): Promise<T> {\n return import(specifier) as Promise<T>;\n}\n\nconst DEFAULT_ENDPOINT = 'http://localhost:4318';\nconst HTTP_INSTRUMENTATION = '@opentelemetry/instrumentation-http';\n\n/**\n * Query parameters whose values are exported as REDACTED in span URLs (see\n * redactUrl). Names are compared without case, `-` or `_`.\n */\nexport const SECRET_QUERY_PARAMS: readonly string[] = [\n 'token',\n 'access_token',\n 'refresh_token',\n 'id_token',\n 'auth_token',\n 'session_token',\n 'api_key',\n 'key',\n 'secret',\n 'client_secret',\n 'password',\n 'passwd',\n 'code',\n 'state',\n 'ticket',\n 'otp',\n 'jwt',\n 'signature',\n 'sig',\n 'X-Amz-Signature',\n 'X-Amz-Credential',\n 'X-Amz-Security-Token',\n 'AWSAccessKeyId',\n 'X-Goog-Signature',\n 'X-Goog-Credential',\n];\n\nconst normalizeParam = (name: string): string => name.toLowerCase().replace(/[-_]/g, '');\n\nfunction decodeParam(name: string): string {\n try {\n return decodeURIComponent(name.replace(/\\+/g, ' '));\n } catch {\n return name;\n }\n}\n\n/**\n * `url` (absolute, or a path) with the value of each query parameter named in\n * `params` replaced by REDACTED, the rest kept as it was. Telemetry exported\n * URLs as they were requested: `?api_key=`, `?access_token=`, a presigned\n * URL's signature.\n */\nexport function redactUrl(url: string, params: readonly string[] = SECRET_QUERY_PARAMS): string {\n const start = url.indexOf('?');\n if (start === -1 || params.length === 0) return url;\n const names = new Set(params.map(normalizeParam));\n const hash = url.indexOf('#', start);\n const end = hash === -1 ? url.length : hash;\n let redacted = false;\n const query = url\n .slice(start + 1, end)\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n if (eq === -1 || !names.has(normalizeParam(decodeParam(pair.slice(0, eq))))) return pair;\n redacted = true;\n return `${pair.slice(0, eq)}=REDACTED`;\n })\n .join('&');\n return redacted ? url.slice(0, start + 1) + query + url.slice(end) : url;\n}\n\n/** The URL attributes of an HTTP span, in the stable and the older semantic conventions. */\nconst URL_ATTRIBUTES = ['url.full', 'url.query', 'http.url', 'http.target'];\n\n/** Rewrites the URL attributes of a recording SDK span (it has `attributes`). */\nfunction redactSpanUrls(span: unknown, params: readonly string[]): void {\n const { attributes, setAttribute } = (span ?? {}) as {\n attributes?: Record<string, unknown>;\n setAttribute?: (key: string, value: string) => unknown;\n };\n if (!attributes || typeof setAttribute !== 'function') return;\n for (const key of URL_ATTRIBUTES) {\n const value = attributes[key];\n if (typeof value !== 'string') continue;\n // url.query is the query string alone, without its \"?\".\n const redacted = key === 'url.query' ? redactUrl(`?${value}`, params).slice(1) : redactUrl(value, params);\n if (redacted !== value) setAttribute.call(span, key, redacted);\n }\n}\n\n/**\n * The options initOtel() passes to getNodeAutoInstrumentations(): the app's\n * `instrumentations` over Iskra's defaults. The HTTP instrumentation redacts\n * SECRET_QUERY_PARAMS unless `redactedQueryParams` / `redactedQueryParamsServer`\n * are set (instrumentation-http 0.204 / 0.222 and later apply them itself;\n * for older releases a requestHook rewrites the span's URL attributes, before\n * the app's own requestHook runs).\n */\nexport function autoInstrumentationOptions(config: OtelConfig): Record<string, unknown> {\n const http: Record<string, unknown> = { ...config.instrumentations?.[HTTP_INSTRUMENTATION] };\n const list = (value: unknown) => (Array.isArray(value) ? (value as string[]) : undefined);\n const client = list(http.redactedQueryParams) ?? SECRET_QUERY_PARAMS;\n const server = list(http.redactedQueryParamsServer) ?? SECRET_QUERY_PARAMS;\n const appHook = http.requestHook;\n return {\n '@opentelemetry/instrumentation-fs': { enabled: false },\n ...config.instrumentations,\n [HTTP_INSTRUMENTATION]: {\n ...http,\n redactedQueryParams: client,\n redactedQueryParamsServer: server,\n requestHook: (span: unknown, request: unknown) => {\n // A ClientRequest (outgoing) has setHeader(); an IncomingMessage does not.\n const outgoing = typeof (request as { setHeader?: unknown } | null)?.setHeader === 'function';\n redactSpanUrls(span, outgoing ? client : server);\n if (typeof appHook === 'function') appHook(span, request);\n },\n },\n };\n}\n\n/** Hosts spans may reach in clear text: this machine, a private network, or a name that only resolves inside one. */\nfunction isPrivateHost(hostname: string): boolean {\n const host = hostname.replace(/^\\[|\\]$/g, '').toLowerCase();\n if (host.includes(':')) return host === '::1' || /^f[cd]/.test(host);\n return (\n !host.includes('.') ||\n /\\.(localhost|local|internal)$/.test(host) ||\n /^(127|10)\\./.test(host) ||\n /^192\\.168\\./.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(host)\n );\n}\n\n/**\n * The OTLP endpoint as it is safe to log, its origin (the path, query or\n * userinfo can carry an API key), and whether spans travel to it in clear\n * text beyond this machine and its private network.\n */\nexport function describeOtelEndpoint(config: OtelConfig): { origin: string; plaintext: boolean } {\n try {\n const url = new URL(config.endpoint || DEFAULT_ENDPOINT);\n return { origin: url.origin, plaintext: url.protocol === 'http:' && !isPrivateHost(url.hostname) };\n } catch {\n return { origin: '(invalid URL)', plaintext: false };\n }\n}\n\n/**\n * Builds a Resource with whichever API the installed `@opentelemetry/resources`\n * provides: 2.x only exports `resourceFromAttributes()` (`Resource` is a type\n * there, so `new Resource()` throws), 1.x exports the `Resource` class.\n */\nexport function createResource(resources: OtelResourcesModule, attributes: Record<string, string>): unknown {\n if (typeof resources?.resourceFromAttributes === 'function') {\n return resources.resourceFromAttributes(attributes);\n }\n if (typeof resources?.Resource === 'function') {\n return new resources.Resource(attributes);\n }\n throw new Error(\n '[iskra/otel] Unsupported @opentelemetry/resources: it exports neither resourceFromAttributes() nor Resource',\n );\n}\n\n/**\n * Initialize the OpenTelemetry NodeSDK with OTLP exporters.\n * Must be called before any drivers start so auto-instrumentation can patch libraries.\n */\nexport async function initOtel(config: OtelConfig, appName: string): Promise<void> {\n if (sdkInstance) return; // Already initialized\n\n try {\n const [\n { NodeSDK },\n { getNodeAutoInstrumentations },\n { OTLPTraceExporter },\n { OTLPMetricExporter },\n { PeriodicExportingMetricReader },\n resources,\n semconv,\n ] = await Promise.all([\n importOptional<OtelSdkNode>(OTEL_MODULES.sdkNode),\n importOptional<OtelAutoInstrumentations>(OTEL_MODULES.autoInstrumentations),\n importOptional<OtelTraceExporter>(OTEL_MODULES.traceExporter),\n importOptional<OtelMetricExporter>(OTEL_MODULES.metricExporter),\n importOptional<OtelSdkMetrics>(OTEL_MODULES.sdkMetrics),\n importOptional<OtelResourcesModule>(OTEL_MODULES.resources),\n importOptional<OtelSemconv>(OTEL_MODULES.semconv),\n ]);\n\n const serviceName = config.serviceName || appName;\n const endpoint = config.endpoint || DEFAULT_ENDPOINT;\n\n const resource = createResource(resources, {\n [semconv.ATTR_SERVICE_NAME ?? 'service.name']: serviceName,\n [semconv.ATTR_SERVICE_VERSION ?? 'service.version']: config.serviceVersion || '0.1.0',\n 'deployment.environment': config.environment || nodeEnv() || 'development',\n ...config.resourceAttributes,\n });\n\n const instrumentationOverrides = autoInstrumentationOptions(config);\n\n const sdk = new NodeSDK({\n resource,\n traceExporter: new OTLPTraceExporter({ url: `${endpoint}/v1/traces` }),\n metricReader: new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ url: `${endpoint}/v1/metrics` }),\n exportIntervalMillis: config.metricIntervalMs ?? 60_000,\n }),\n instrumentations: [getNodeAutoInstrumentations(instrumentationOverrides)],\n });\n\n sdk.start();\n sdkInstance = sdk;\n } catch (err) {\n // Provide a clear error when OTel packages are not installed\n const e = err as { code?: string; message?: string } | null;\n if (\n e?.code === 'ERR_MODULE_NOT_FOUND' ||\n e?.code === 'MODULE_NOT_FOUND' ||\n e?.message?.includes('Cannot find')\n ) {\n throw new Error(\n `[iskra/otel] OpenTelemetry packages are not installed. ` +\n `Install them with: bun add @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node ` +\n `@opentelemetry/exporter-trace-otlp-http @opentelemetry/exporter-metrics-otlp-http ` +\n `@opentelemetry/sdk-metrics @opentelemetry/resources @opentelemetry/semantic-conventions`,\n );\n }\n throw err;\n }\n}\n\n/**\n * Gracefully shut down the OTel SDK, flushing any pending spans/metrics.\n */\nexport async function shutdownOtel(): Promise<void> {\n if (sdkInstance) {\n await sdkInstance.shutdown();\n sdkInstance = null;\n }\n}\n"],"mappings":"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","names":["result"]}
1
+ {"version":3,"sources":["../src/app.ts","../src/logger/index.ts","../src/env.ts","../src/config/loader.ts","../src/config/schema.ts","../src/errors.ts","../src/otel.ts","../src/traced.ts"],"sourcesContent":["import mitt, { type Emitter } from 'mitt';\nimport { createLogger, type Logger } from './logger';\nimport { loadAppConfig } from './config/loader';\nimport type { AppConfig, AppContextRegistry, AppEvents, Driver, Plugin, Context } from './types';\nimport { LifecycleError } from './errors';\nimport { describeOtelEndpoint, initOtel, shutdownOtel } from './otel';\nimport { nodeEnv } from './env';\n\n/**\n * `app.context`: a Map whose keys in AppContextRegistry (`'db'`, `'kv'`, …)\n * are typed; any other key holds `unknown`, or the type given as `T`.\n */\nexport class AppContext extends Map<string, unknown> {\n override get<K extends keyof AppContextRegistry>(key: K): AppContextRegistry[K] | undefined;\n override get<T = unknown>(key: string): T | undefined;\n override get(key: string): unknown {\n return super.get(key);\n }\n\n override set<K extends keyof AppContextRegistry>(key: K, value: AppContextRegistry[K]): this;\n override set(key: string, value: unknown): this;\n override set(key: string, value: unknown): this {\n return super.set(key, value);\n }\n}\n\nconst DEFAULT_SHUTDOWN_SIGNALS: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;\n\nexport class App {\n public config: AppConfig;\n public logger: Logger;\n public events: Emitter<Record<string, unknown>>;\n private drivers: Driver[] = [];\n /** Drivers whose start() succeeded, in start order; null until start() runs. */\n private startedDrivers: Driver[] | null = null;\n private pendingInstalls: PromiseLike<void>[] = [];\n /** The shutdown in progress, shared by concurrent stop() calls. */\n private stopping: Promise<void> | null = null;\n private signalHandler?: (signal: NodeJS.Signals) => void;\n private signals: NodeJS.Signals[] = [];\n public context = new AppContext();\n\n constructor(config?: AppConfig) {\n this.events = mitt();\n // Temporary config until init() is called (or passed in constructor)\n this.config = config || ({ name: 'Bootstrapping', logger: { level: 'info' } } as AppConfig);\n this.logger = createLogger(this.config.name, this.config.logger?.level);\n }\n\n async init() {\n // If config was not passed, load it\n if (this.config.name === 'Bootstrapping') {\n this.config = await loadAppConfig();\n this.logger = createLogger(this.config.name, this.config.logger?.level);\n }\n\n // Initialize OpenTelemetry SDK before drivers (so auto-instrumentation patches libraries)\n if (this.config.otel && this.config.otel.enabled !== false) {\n await initOtel(this.config.otel, this.config.name);\n // Its origin only: the endpoint's path, query or userinfo can hold an API key.\n const { origin, plaintext } = describeOtelEndpoint(this.config.otel);\n this.logger.info({ endpoint: origin }, 'OpenTelemetry initialized');\n if (plaintext) {\n this.logger.warn(\n { endpoint: origin },\n 'OpenTelemetry endpoint is plain http:// on a remote host: spans (URLs, captured headers) travel unencrypted; use https://',\n );\n }\n }\n\n this.logger.info('Initializing App...');\n\n // Async plugin installs are awaited here so a failure surfaces from\n // start() instead of crashing the process as an unhandled rejection.\n await Promise.all(this.pendingInstalls);\n this.pendingInstalls = [];\n\n // Only the drivers whose init() ran are rolled back: one without an\n // init hook was never initialized and has nothing to release yet.\n const initialized: Driver[] = [];\n for (const driver of this.drivers) {\n if (!driver.init) continue;\n this.logger.debug(`Initializing driver: ${driver.name}`);\n initialized.push(driver);\n try {\n await driver.init(this);\n } catch (err) {\n // The failing driver may hold part of what it opened (a pool, a\n // child process), so it is stopped too, with the ones before it.\n this.logger.error(\n { err, driver: driver.name },\n 'Driver failed to initialize; stopping the drivers already initialized',\n );\n await this.rollback(initialized.reverse());\n throw err;\n }\n }\n }\n\n register(driver: Driver) {\n this.drivers.push(driver);\n return this;\n }\n\n use(plugin: Plugin) {\n const result = plugin.install(this);\n if (result && typeof (result as PromiseLike<void>).then === 'function') {\n const install = Promise.resolve(result);\n // Observed now so an early rejection is not reported as unhandled;\n // init() awaits the original promise and rethrows its error.\n install.catch(() => {});\n this.pendingInstalls.push(install);\n }\n return this;\n }\n\n /**\n * Runs `handler` for every `event`. Events in AppEvents give the handler a\n * typed payload; for others it is `unknown`, or the type given as `T`.\n */\n on<K extends keyof AppEvents>(event: K, handler: (ctx: Context<AppEvents[K]>) => Promise<void> | void): this;\n on<T = unknown>(event: string, handler: (ctx: Context<T>) => Promise<void> | void): this;\n on(event: string, handler: (ctx: Context<unknown>) => Promise<void> | void): this {\n this.events.on(event, async (payload) => {\n const ctx: Context<unknown> = {\n app: this,\n logger: this.logger.child({ event }),\n payload,\n reply: (data) => this.events.emit(`${event}:reply`, data),\n };\n\n try {\n await handler(ctx);\n } catch (err) {\n this.logger.error({ err, event }, 'Error handling event');\n }\n });\n return this;\n }\n\n /** Emits `event`; for events in AppEvents the payload must match their type. */\n emit<K extends keyof AppEvents>(event: K, payload: AppEvents[K]): void;\n emit(event: string, payload?: unknown): void;\n emit(event: string, payload?: unknown): void {\n this.events.emit(event, payload);\n }\n\n /**\n * Starts drivers one at a time, in registration order, so a driver can rely\n * on the ones registered before it (e.g. the DB before the web server). If\n * one fails, every driver is stopped in reverse order (all were initialized\n * and may hold connections, including the failing one), OpenTelemetry is\n * shut down and the error is rethrown, instead of leaving ports bound and\n * children running.\n */\n async start() {\n await this.init();\n if (!nodeEnv()) {\n this.logger.warn(\n 'NODE_ENV is not set: production defaults apply (Secure cookies, no sample secrets, JSON logs). ' +\n 'Set NODE_ENV=development for local development.',\n );\n }\n this.logger.info('Starting drivers...');\n const started: Driver[] = [];\n for (const driver of this.drivers) {\n try {\n if (driver.start) await driver.start();\n started.push(driver);\n } catch (err) {\n this.logger.error({ err, driver: driver.name }, 'Driver failed to start; stopping every driver');\n await this.rollback([...this.drivers].reverse());\n throw err;\n }\n }\n this.startedDrivers = started;\n this.installSignalHandlers();\n this.logger.info('App started successfully 🚀');\n }\n\n /**\n * Stops drivers in reverse start order (the web server before the DB it\n * uses), continuing past failures, and always flushes OpenTelemetry.\n *\n * A call made while a stop is in progress (e.g. an app's own signal\n * handler next to the App's) gets that same stop: it resolves only once\n * every driver has stopped, instead of right away with nothing left to do.\n */\n stop(): Promise<void> {\n this.stopping ??= this.stopOnce().finally(() => {\n this.stopping = null;\n });\n return this.stopping;\n }\n\n private async stopOnce() {\n this.logger.info('Stopping app...');\n const toStop = [...(this.startedDrivers ?? this.drivers)].reverse();\n this.startedDrivers = [];\n\n let failures: PromiseRejectedResult[];\n try {\n failures = await this.stopDrivers(toStop);\n } finally {\n // Shutdown OTel SDK after drivers (flush pending spans/metrics)\n await shutdownOtel();\n // Removed only now: while the drivers stop, a second signal must\n // still reach the handler (forced exit) instead of the default action.\n this.removeSignalHandlers();\n }\n\n if (failures.length > 0) {\n this.logger.error({ failures }, 'Some drivers failed to stop');\n throw new LifecycleError('Some drivers failed to stop', {\n failures,\n context: { driverCount: toStop.length, failedCount: failures.length },\n });\n }\n\n this.logger.info('App stopped.');\n }\n\n /**\n * Stops `drivers` after a failed init() or start() and shuts down\n * OpenTelemetry. Failures to stop are logged, not thrown: the caller\n * rethrows the original error. A later stop() stops nothing more.\n */\n private async rollback(drivers: Driver[]) {\n this.startedDrivers = [];\n await this.stopDrivers(drivers);\n try {\n await shutdownOtel();\n } catch (err) {\n this.logger.error({ err }, 'OpenTelemetry failed to shut down');\n }\n }\n\n private async stopDrivers(drivers: Driver[]): Promise<PromiseRejectedResult[]> {\n const failures: PromiseRejectedResult[] = [];\n for (const driver of drivers) {\n try {\n if (driver.stop) await driver.stop();\n } catch (reason) {\n this.logger.error({ err: reason, driver: driver.name }, 'Driver failed to stop');\n failures.push({ status: 'rejected', reason });\n }\n }\n return failures;\n }\n\n /**\n * Stops the app gracefully on SIGTERM/SIGINT (configurable with\n * `shutdownSignals`, off by default under NODE_ENV=test). Once a listener\n * exists the runtime no longer exits on its own, so the handler exits with\n * 0 after a clean stop, or 1 on failure / after `shutdownTimeoutMs`. A\n * second signal forces exit.\n */\n private installSignalHandlers() {\n const configured = this.config.shutdownSignals;\n const signals =\n configured === false ? [] : (configured ?? (nodeEnv() === 'test' ? [] : DEFAULT_SHUTDOWN_SIGNALS));\n if (signals.length === 0 || this.signalHandler) return;\n\n const timeoutMs = this.config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n let shuttingDown = false;\n this.signalHandler = (signal) => {\n if (shuttingDown) {\n this.logger.warn({ signal }, 'Second shutdown signal; exiting immediately');\n process.exit(1);\n }\n shuttingDown = true;\n this.logger.info({ signal }, 'Shutdown signal received; stopping app');\n const timer = setTimeout(() => {\n this.logger.error({ timeoutMs }, 'Graceful shutdown timed out; exiting');\n process.exit(1);\n }, timeoutMs);\n this.stop().then(\n () => {\n clearTimeout(timer);\n process.exit(0);\n },\n (err) => {\n clearTimeout(timer);\n this.logger.error({ err }, 'Graceful shutdown failed');\n process.exit(1);\n },\n );\n };\n this.signals = signals as NodeJS.Signals[];\n for (const signal of this.signals) process.on(signal, this.signalHandler);\n }\n\n private removeSignalHandlers() {\n if (!this.signalHandler) return;\n for (const signal of this.signals) process.off(signal, this.signalHandler);\n this.signalHandler = undefined;\n this.signals = [];\n }\n}\n","import pino from 'pino';\nimport pretty from 'pino-pretty';\nimport { isDevelopmentEnv } from '../env';\n\n/**\n * Field names whose values are replaced with `[REDACTED]`, at any depth. Keys\n * are compared lowercased and without `-` or `_`, so `apiKey`, `api_key` and\n * `X-API-Key` are the same key.\n */\nconst REDACTED_KEYS = [\n 'password',\n 'pass',\n 'passwd',\n 'apiKey',\n 'apiSecret',\n 'token',\n 'authToken',\n 'accessToken',\n 'refreshToken',\n 'idToken',\n 'secret',\n 'clientSecret',\n 'secretKey',\n 'privateKey',\n 'authorization',\n 'proxyAuthorization',\n 'cookie',\n 'setCookie',\n 'sessionId',\n] as const;\n\n/**\n * Keys ending in one of these are redacted too (normalized the same way):\n * `dbPassword`, `x-api-key`, `x-auth-token`, `AWS_SECRET_ACCESS_KEY`,\n * `webhookSecret`.\n */\nconst REDACTED_SUFFIXES = ['password', 'passwd', 'secret', 'token', 'apikey', 'secretkey', 'privatekey', 'accesskey'];\n\nconst normalizeKey = (key: string): string => key.toLowerCase().replace(/[-_]/g, '');\nconst SENSITIVE = new Set<string>(REDACTED_KEYS.map(normalizeKey));\nconst isSensitive = (key: string): boolean => {\n const normalized = normalizeKey(key);\n return SENSITIVE.has(normalized) || REDACTED_SUFFIXES.some((suffix) => normalized.endsWith(suffix));\n};\nconst CENSOR = '[REDACTED]';\n/** Objects deeper than this are logged as they are. */\nconst MAX_DEPTH = 8;\nconst IN_PROGRESS = Symbol('in progress');\n\n/** The prototype of what pino's err serializer returns (an error's fields, causes included). */\nconst SERIALIZED_ERROR_PROTO = Object.getPrototypeOf(pino.stdSerializers.err(new Error()));\n\nconst isPlainObject = (value: object): boolean => {\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null || proto === SERIALIZED_ERROR_PROTO;\n};\n\n/** Query parameter names whose values are masked in messages. */\nconst SECRET_PARAM = /pass|secret|token|key|sig|auth/i;\n\n/**\n * Passwords in `scheme://user:password@host` and secret-looking query\n * parameters (`?authToken=`, `&X-Amz-Signature=`) of a message: drivers put\n * the connection string in the errors they throw. Bounded quantifiers keep\n * both patterns linear on long input.\n */\nfunction maskCredentials(text: string): string {\n return text\n .replace(/\\b([a-z][\\w+.-]{0,30}:\\/\\/[^\\s:@/]{0,256}):[^\\s@/]{1,256}@/gi, '$1:[REDACTED]@')\n .replace(/([?&])([\\w.-]{1,100})=([^&\\s'\"#]+)/g, (match, sep: string, name: string) =>\n SECRET_PARAM.test(name) ? `${sep}${name}=[REDACTED]` : match,\n );\n}\n\n/**\n * An Error as pino's err serializer writes it (type, message and stack with\n * their causes, and its own fields), scrubbed. pino serializes errors after\n * the log formatter runs, so an HTTP client's error (`config.headers\n * .Authorization`) or a Redis error (`command.args` of AUTH) went out as is.\n */\nfunction scrubError(err: Error, depth: number, done: WeakMap<object, unknown>): unknown {\n const serialized = { ...pino.stdSerializers.err(err) } as Record<string, unknown>;\n if (typeof serialized.message === 'string') serialized.message = maskCredentials(serialized.message);\n if (typeof serialized.stack === 'string') serialized.stack = maskCredentials(serialized.stack);\n // Deep: an error keeps the client's objects (axios's `request`, whose\n // `_options.headers` hold the Authorization header), which are class\n // instances that JSON.stringify writes out in full.\n return scrubEntries(serialized, depth, done, true);\n}\n\n/**\n * Replaces the value of every sensitive key, at any depth, in a copy of the\n * objects that contain one (the caller's objects are not modified). pino's\n * own `redact` has no recursive wildcard, and listing each key at every depth\n * made logging 14 times slower. Errors are serialized and scrubbed here (see\n * scrubError), and objects with a toJSON() are scrubbed as what it returns;\n * other class instances are left to pino, except inside an error.\n */\nfunction scrub(value: unknown, depth: number, done: WeakMap<object, unknown>, deep = false): unknown {\n if (value === null || typeof value !== 'object' || depth > MAX_DEPTH) return value;\n if (done.has(value)) {\n const result = done.get(value);\n // Still being walked: a cycle, which pino would print as [Circular].\n return result === IN_PROGRESS ? '[Circular]' : result;\n }\n const isError = value instanceof Error;\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n const walk = isError || Array.isArray(value) || isPlainObject(value) || typeof toJSON === 'function' || deep;\n if (!walk) return value;\n done.set(value, IN_PROGRESS);\n let result: unknown;\n if (isError) {\n result = scrubError(value, depth, done);\n } else if (Array.isArray(value)) {\n let copy: unknown[] | undefined;\n value.forEach((item, i) => {\n const scrubbed = scrub(item, depth + 1, done, deep);\n if (scrubbed !== item) (copy ??= value.slice())[i] = scrubbed;\n });\n result = copy ?? value;\n } else if (isPlainObject(value)) {\n result = scrubEntries(value as Record<string, unknown>, depth, done, deep);\n } else if (typeof toJSON === 'function') {\n // Written as its toJSON(), which is what JSON.stringify writes: an axios\n // error's `config.headers` is an AxiosHeaders instance whose\n // Authorization header went out as is.\n result = scrub(toJSON.call(value), depth, done, deep);\n } else {\n // Inside an error: another class instance, as JSON.stringify writes it\n // (its own enumerable fields).\n result = scrubEntries({ ...(value as Record<string, unknown>) }, depth, done, deep);\n }\n done.set(value, result);\n return result;\n}\n\n/** An object with its sensitive keys censored and its values scrubbed; a copy if anything changed. */\nfunction scrubEntries(\n value: Record<string, unknown>,\n depth: number,\n done: WeakMap<object, unknown>,\n deep = false,\n): Record<string, unknown> {\n let copy: Record<string, unknown> | undefined;\n for (const [key, item] of Object.entries(value)) {\n const scrubbed = isSensitive(key) ? CENSOR : scrub(item, depth + 1, done, deep);\n if (scrubbed !== item) (copy ??= { ...value })[key] = scrubbed;\n }\n // A nested serialized error (pino's prototype) becomes a plain object either way.\n return copy ?? (Object.getPrototypeOf(value) === SERIALIZED_ERROR_PROTO ? { ...value } : value);\n}\n\nexport const createLogger = (name: string, level: string = 'info') => {\n // Pretty output only in development/test: JSON otherwise, NODE_ENV unset included.\n const isDev = isDevelopmentEnv();\n const options: pino.LoggerOptions = {\n name,\n level,\n formatters: {\n log: (object) => scrub(object, 0, new WeakMap()) as Record<string, unknown>,\n },\n // The formatter above already serialized errors (scrubbed): pino's own\n // err serializer would take that plain object for an error again and\n // rewrite its `type` as \"Object\".\n serializers: {\n err: (value: unknown) => (value instanceof Error ? scrub(value, 0, new WeakMap()) : value),\n },\n hooks: {\n // Messages are strings the formatter never sees: mask connection\n // string passwords there too, including the message pino takes\n // from an error logged on its own (`logger.error(err)`).\n logMethod(args, method) {\n const masked: unknown[] = args.map((arg) => (typeof arg === 'string' ? maskCredentials(arg) : arg));\n if (masked[0] instanceof Error && typeof masked[1] !== 'string') {\n masked.splice(1, 0, maskCredentials(masked[0].message));\n }\n return method.apply(this, masked as Parameters<typeof method>);\n },\n },\n redact: {\n paths: ['config.env', '*.data'],\n censor: CENSOR,\n },\n };\n // pino-pretty as an in-process stream, not a `transport`: a transport runs\n // in a worker thread that loads the module by name at runtime, which fails\n // in a `bun build --compile` binary and crashed it at startup.\n const logger = isDev ? pino(options, pretty({ colorize: true })) : pino(options);\n\n // Bindings (`logger.child({ ... })`) do not go through formatters.log:\n // scrub them here. A child's own children inherit this child().\n type Child = (this: pino.Logger, bindings: pino.Bindings, options?: object) => pino.Logger;\n const child = logger.child as unknown as Child;\n logger.child = function (this: pino.Logger, bindings: pino.Bindings, childOptions?: object) {\n return child.call(this, scrub(bindings, 0, new WeakMap()) as pino.Bindings, childOptions);\n } as unknown as typeof logger.child;\n return logger;\n};\n\nexport type Logger = pino.Logger;\n","/**\n * The deployment environment, read when called: `NODE_ENV` through a computed\n * key, because `bun build` replaces a literal `process.env.NODE_ENV` with its\n * value at build time (\"development\" when unset), so a compiled binary ignored\n * the NODE_ENV it ran with.\n */\nconst NODE_ENV = 'NODE_ENV';\n\n/** `NODE_ENV` as the process runs with it, or undefined when unset or empty. */\nexport function nodeEnv(): string | undefined {\n return process.env[NODE_ENV] || undefined;\n}\n\n/**\n * Whether development conveniences apply (pretty logs, http auth URLs, sample\n * secrets, `disableCSRFCheck`): only with `NODE_ENV` set to `development` or\n * `test`.\n */\nexport function isDevelopmentEnv(env: string | undefined = nodeEnv()): boolean {\n return env === 'development' || env === 'test';\n}\n\n/**\n * Whether production safeguards apply: in every environment but `development`\n * and `test`, including an unset NODE_ENV and names like `staging`. They used\n * to apply only to NODE_ENV=production exactly, so a deploy that forgot it\n * sent non-Secure cookies and accepted placeholder secrets.\n */\nexport function isProductionEnv(env: string | undefined = nodeEnv()): boolean {\n return !isDevelopmentEnv(env);\n}\n","import { loadConfig } from 'c12';\nimport { AppConfigSchema } from './schema';\nimport type { AppConfig } from '../types';\n\nexport async function loadAppConfig(cwd: string = process.cwd()): Promise<AppConfig> {\n const { config } = await loadConfig<AppConfig>({\n name: 'app',\n configFile: 'app.config',\n cwd,\n dotenv: true,\n // c12 would also merge a `.apprc` file from the working directory (a\n // plain key=value file that could add `processes` to spawn or move\n // `otel.endpoint`), and download and run `extends` layers from\n // github:/gitlab:/https:// sources on every start. Local `extends`\n // paths still work.\n rcFile: false,\n giget: false,\n });\n\n const validConfig = AppConfigSchema.parse(config || {});\n return validConfig as AppConfig;\n}\n","import { z } from 'zod';\n\nconst RestartBackoffSchema = z.object({\n initialMs: z.number().positive().optional(),\n maxMs: z.number().positive().optional(),\n factor: z.number().positive().optional(),\n});\n\n// Sections owned by the kits are validated here for shape only, and every\n// object is `.passthrough()`: AppConfig promises `[key: string]: any`, and a\n// plain z.object() silently strips unknown keys — which used to drop `db`,\n// `kv` and `socket` from app.config.ts entirely.\nexport const AppConfigSchema = z\n .object({\n name: z.string().default('IskraApp'),\n debug: z.boolean().default(false),\n logger: z\n .object({\n level: z.string().default('info'),\n })\n .passthrough()\n .default({}),\n otel: z\n .object({\n enabled: z.boolean().default(true),\n endpoint: z.string().default('http://localhost:4318'),\n serviceName: z.string().optional(),\n serviceVersion: z.string().default('0.1.0'),\n environment: z.string().optional(),\n metricIntervalMs: z.number().default(60_000),\n resourceAttributes: z.record(z.string()).optional(),\n // passthrough: the instrumentations' own options (hooks, redactedQueryParams) were stripped.\n instrumentations: z.record(z.object({ enabled: z.boolean().optional() }).passthrough()).optional(),\n })\n .passthrough()\n .optional(),\n shutdownSignals: z.union([z.array(z.string()), z.literal(false)]).optional(),\n shutdownTimeoutMs: z.number().positive().optional(),\n processes: z\n .record(\n z\n .object({\n command: z.string(),\n args: z.array(z.string()).optional(),\n mode: z.enum(['daemon', 'oneshot', 'stdio']).default('daemon'),\n restartOnCrash: z.boolean().default(false),\n maxRestarts: z.number().int().nonnegative().optional(),\n restartCooldown: z.number().nonnegative().optional(),\n restartBackoff: RestartBackoffSchema.optional(),\n env: z.record(z.string()).optional(),\n inheritEnv: z.union([z.boolean(), z.array(z.string())]).optional(),\n maxPendingStdinBytes: z.number().int().positive().optional(),\n })\n .passthrough(),\n )\n .optional(),\n socket: z\n .object({\n enabled: z.boolean(),\n port: z.number().optional(),\n adapter: z.enum(['bun', 'socket.io']).optional(),\n })\n .passthrough()\n .optional(),\n kv: z\n .object({\n driver: z.enum(['memory', 'redis']),\n connection: z.any().optional(),\n })\n .passthrough()\n .optional(),\n db: z\n .object({\n driver: z.enum(['postgres', 'mysql', 'sqlite', 'libsql']),\n url: z.string(),\n authToken: z.string().optional(),\n })\n .passthrough()\n .optional(),\n })\n .passthrough();\n\nexport type AppConfigInput = z.input<typeof AppConfigSchema>;\nexport type AppConfigOutput = z.output<typeof AppConfigSchema>;\n","export const ErrorCodes = {\n // General\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n NOT_INITIALIZED: 'NOT_INITIALIZED',\n ADAPTER_ERROR: 'ADAPTER_ERROR',\n\n // Config\n CONFIG_INVALID: 'CONFIG_INVALID',\n CONFIG_MISSING: 'CONFIG_MISSING',\n\n // Driver\n DRIVER_INIT_FAILED: 'DRIVER_INIT_FAILED',\n DRIVER_START_FAILED: 'DRIVER_START_FAILED',\n DRIVER_STOP_FAILED: 'DRIVER_STOP_FAILED',\n DRIVER_NOT_FOUND: 'DRIVER_NOT_FOUND',\n\n // Plugin\n PLUGIN_INSTALL_FAILED: 'PLUGIN_INSTALL_FAILED',\n\n // Lifecycle\n LIFECYCLE_START_FAILED: 'LIFECYCLE_START_FAILED',\n LIFECYCLE_STOP_FAILED: 'LIFECYCLE_STOP_FAILED',\n\n // HTTP\n BAD_REQUEST: 'BAD_REQUEST',\n UNAUTHORIZED: 'UNAUTHORIZED',\n FORBIDDEN: 'FORBIDDEN',\n NOT_FOUND: 'NOT_FOUND',\n METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',\n CONFLICT: 'CONFLICT',\n PAYLOAD_TOO_LARGE: 'PAYLOAD_TOO_LARGE',\n UNSUPPORTED_MEDIA_TYPE: 'UNSUPPORTED_MEDIA_TYPE',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n RATE_LIMITED: 'RATE_LIMITED',\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n TIMEOUT: 'TIMEOUT',\n\n // Data\n DATABASE_ERROR: 'DATABASE_ERROR',\n CONNECTION_ERROR: 'CONNECTION_ERROR',\n QUERY_ERROR: 'QUERY_ERROR',\n MIGRATION_ERROR: 'MIGRATION_ERROR',\n CACHE_ERROR: 'CACHE_ERROR',\n\n // Storage / Upload\n STORAGE_ERROR: 'STORAGE_ERROR',\n FILE_UPLOAD_ERROR: 'FILE_UPLOAD_ERROR',\n FILE_NOT_FOUND: 'FILE_NOT_FOUND',\n\n // Worker\n QUEUE_ERROR: 'QUEUE_ERROR',\n JOB_ERROR: 'JOB_ERROR',\n\n // Socket\n SOCKET_CONNECTION_ERROR: 'SOCKET_CONNECTION_ERROR',\n SOCKET_MESSAGE_ERROR: 'SOCKET_MESSAGE_ERROR',\n} as const;\n\n/**\n * Every error code, as keys: Iskra's own (`ErrorCodes`) and the ones an app\n * adds by merging into this interface, which `ErrorCode` then accepts:\n *\n * ```ts\n * declare module '@iskra-bun/core' {\n * interface ErrorCodeRegistry {\n * ORDER_LOCKED: true;\n * }\n * }\n * throw new HttpError(423, 'The order is being edited', { code: 'ORDER_LOCKED' });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- extended by declaration merging\nexport interface ErrorCodeRegistry extends Record<(typeof ErrorCodes)[keyof typeof ErrorCodes], true> {}\n\nexport type ErrorCode = keyof ErrorCodeRegistry & string;\n\nexport interface IskraErrorOptions {\n code: ErrorCode;\n cause?: Error;\n context?: Record<string, unknown>;\n}\n\n/**\n * Clase base para todos los errores del framework Iskra.\n * Soporta encadenamiento de errores vía `cause` y metadata arbitraria en `context`.\n */\nexport class IskraError extends Error {\n public readonly code: ErrorCode;\n public readonly context: Record<string, unknown>;\n\n constructor(message: string, options: IskraErrorOptions) {\n super(message, { cause: options.cause });\n this.name = 'IskraError';\n this.code = options.code;\n this.context = options.context ?? {};\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n context: this.context,\n ...(this.cause instanceof Error ? { cause: this.cause.message } : {}),\n };\n }\n}\n\n// ─── Config Errors ───────────────────────────────────────────────────────────\n\nexport class ConfigError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.CONFIG_INVALID, ...options });\n this.name = 'ConfigError';\n }\n}\n\n// ─── Driver Errors ───────────────────────────────────────────────────────────\n\nexport class DriverError extends IskraError {\n constructor(message: string, options?: { code?: ErrorCode; cause?: Error; context?: Record<string, unknown> }) {\n super(message, {\n code: options?.code ?? ErrorCodes.DRIVER_INIT_FAILED,\n cause: options?.cause,\n context: options?.context,\n });\n this.name = 'DriverError';\n }\n}\n\n// ─── Plugin Errors ───────────────────────────────────────────────────────────\n\nexport class PluginError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.PLUGIN_INSTALL_FAILED, ...options });\n this.name = 'PluginError';\n }\n}\n\n// ─── Lifecycle Errors ────────────────────────────────────────────────────────\n\nexport class LifecycleError extends IskraError {\n public readonly failures: PromiseRejectedResult[];\n\n constructor(\n message: string,\n options: { failures?: PromiseRejectedResult[]; cause?: Error; context?: Record<string, unknown> },\n ) {\n super(message, { code: ErrorCodes.LIFECYCLE_STOP_FAILED, cause: options.cause, context: options.context });\n this.name = 'LifecycleError';\n this.failures = options.failures ?? [];\n }\n}\n","/**\n * OpenTelemetry SDK initialization for Iskra apps.\n *\n * All OTel packages are dynamic-imported so they remain optional peer deps.\n * Apps that don't set `config.otel` never load this code.\n *\n * The module specifiers are held in variables rather than passed as string\n * literals to `import()`. This stops `tsc` from statically resolving the\n * optional `@opentelemetry/*` packages at compile time, so the module\n * typechecks cleanly even when those peer deps are absent. At runtime the\n * imports still resolve normally, and a missing package degrades to a clear,\n * actionable error instead of an unhandled module-resolution failure.\n */\nimport type { OtelConfig } from './types';\nimport { nodeEnv } from './env';\n\nlet sdkInstance: { shutdown(): Promise<void> } | null = null;\n\n/** Optional OTel package specifiers, indirected so tsc does not resolve them statically. */\nconst OTEL_MODULES = {\n sdkNode: '@opentelemetry/sdk-node',\n autoInstrumentations: '@opentelemetry/auto-instrumentations-node',\n traceExporter: '@opentelemetry/exporter-trace-otlp-http',\n metricExporter: '@opentelemetry/exporter-metrics-otlp-http',\n sdkMetrics: '@opentelemetry/sdk-metrics',\n resources: '@opentelemetry/resources',\n semconv: '@opentelemetry/semantic-conventions',\n} as const;\n\n// The optional OTel packages are loaded by specifier, untyped; these are the\n// parts of them used here.\ntype Ctor<T = unknown> = new (options: Record<string, unknown>) => T;\ninterface OtelSdkNode {\n NodeSDK: Ctor<{ start(): void; shutdown(): Promise<void> }>;\n}\ninterface OtelAutoInstrumentations {\n getNodeAutoInstrumentations(overrides: Record<string, unknown>): unknown;\n}\ninterface OtelTraceExporter {\n OTLPTraceExporter: Ctor;\n}\ninterface OtelMetricExporter {\n OTLPMetricExporter: Ctor;\n}\ninterface OtelSdkMetrics {\n PeriodicExportingMetricReader: Ctor;\n}\ninterface OtelSemconv {\n ATTR_SERVICE_NAME?: string;\n ATTR_SERVICE_VERSION?: string;\n}\n/** `@opentelemetry/resources`: 2.x exports resourceFromAttributes(), 1.x the Resource class. */\nexport interface OtelResourcesModule {\n resourceFromAttributes?: (attributes: Record<string, string>) => unknown;\n Resource?: new (attributes: Record<string, string>) => unknown;\n}\n\n/** Import an optional module by specifier; the indirection keeps tsc from resolving it. */\nfunction importOptional<T>(specifier: string): Promise<T> {\n return import(specifier) as Promise<T>;\n}\n\nconst DEFAULT_ENDPOINT = 'http://localhost:4318';\nconst HTTP_INSTRUMENTATION = '@opentelemetry/instrumentation-http';\n\n/**\n * Query parameters whose values are exported as REDACTED in span URLs (see\n * redactUrl). Names are compared without case, `-` or `_`.\n */\nexport const SECRET_QUERY_PARAMS: readonly string[] = [\n 'token',\n 'access_token',\n 'refresh_token',\n 'id_token',\n 'auth_token',\n 'session_token',\n 'api_key',\n 'key',\n 'secret',\n 'client_secret',\n 'password',\n 'passwd',\n 'code',\n 'state',\n 'ticket',\n 'otp',\n 'jwt',\n 'signature',\n 'sig',\n 'X-Amz-Signature',\n 'X-Amz-Credential',\n 'X-Amz-Security-Token',\n 'AWSAccessKeyId',\n 'X-Goog-Signature',\n 'X-Goog-Credential',\n];\n\nconst normalizeParam = (name: string): string => name.toLowerCase().replace(/[-_]/g, '');\n\nfunction decodeParam(name: string): string {\n try {\n return decodeURIComponent(name.replace(/\\+/g, ' '));\n } catch {\n return name;\n }\n}\n\n/**\n * `url` (absolute, or a path) with the value of each query parameter named in\n * `params` replaced by REDACTED, the rest kept as it was. Telemetry exported\n * URLs as they were requested: `?api_key=`, `?access_token=`, a presigned\n * URL's signature.\n */\nexport function redactUrl(url: string, params: readonly string[] = SECRET_QUERY_PARAMS): string {\n const start = url.indexOf('?');\n if (start === -1 || params.length === 0) return url;\n const names = new Set(params.map(normalizeParam));\n const hash = url.indexOf('#', start);\n const end = hash === -1 ? url.length : hash;\n let redacted = false;\n const query = url\n .slice(start + 1, end)\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n if (eq === -1 || !names.has(normalizeParam(decodeParam(pair.slice(0, eq))))) return pair;\n redacted = true;\n return `${pair.slice(0, eq)}=REDACTED`;\n })\n .join('&');\n return redacted ? url.slice(0, start + 1) + query + url.slice(end) : url;\n}\n\n/** The URL attributes of an HTTP span, in the stable and the older semantic conventions. */\nconst URL_ATTRIBUTES = ['url.full', 'url.query', 'http.url', 'http.target'];\n\n/** Rewrites the URL attributes of a recording SDK span (it has `attributes`). */\nfunction redactSpanUrls(span: unknown, params: readonly string[]): void {\n const { attributes, setAttribute } = (span ?? {}) as {\n attributes?: Record<string, unknown>;\n setAttribute?: (key: string, value: string) => unknown;\n };\n if (!attributes || typeof setAttribute !== 'function') return;\n for (const key of URL_ATTRIBUTES) {\n const value = attributes[key];\n if (typeof value !== 'string') continue;\n // url.query is the query string alone, without its \"?\".\n const redacted = key === 'url.query' ? redactUrl(`?${value}`, params).slice(1) : redactUrl(value, params);\n if (redacted !== value) setAttribute.call(span, key, redacted);\n }\n}\n\n/**\n * The options initOtel() passes to getNodeAutoInstrumentations(): the app's\n * `instrumentations` over Iskra's defaults. The HTTP instrumentation redacts\n * SECRET_QUERY_PARAMS unless `redactedQueryParams` / `redactedQueryParamsServer`\n * are set (instrumentation-http 0.204 / 0.222 and later apply them itself;\n * for older releases a requestHook rewrites the span's URL attributes, before\n * the app's own requestHook runs).\n */\nexport function autoInstrumentationOptions(config: OtelConfig): Record<string, unknown> {\n const http: Record<string, unknown> = { ...config.instrumentations?.[HTTP_INSTRUMENTATION] };\n const list = (value: unknown) => (Array.isArray(value) ? (value as string[]) : undefined);\n const client = list(http.redactedQueryParams) ?? SECRET_QUERY_PARAMS;\n const server = list(http.redactedQueryParamsServer) ?? SECRET_QUERY_PARAMS;\n const appHook = http.requestHook;\n return {\n '@opentelemetry/instrumentation-fs': { enabled: false },\n ...config.instrumentations,\n [HTTP_INSTRUMENTATION]: {\n ...http,\n redactedQueryParams: client,\n redactedQueryParamsServer: server,\n requestHook: (span: unknown, request: unknown) => {\n // A ClientRequest (outgoing) has setHeader(); an IncomingMessage does not.\n const outgoing = typeof (request as { setHeader?: unknown } | null)?.setHeader === 'function';\n redactSpanUrls(span, outgoing ? client : server);\n if (typeof appHook === 'function') appHook(span, request);\n },\n },\n };\n}\n\n/** Hosts spans may reach in clear text: this machine, a private network, or a name that only resolves inside one. */\nfunction isPrivateHost(hostname: string): boolean {\n const host = hostname.replace(/^\\[|\\]$/g, '').toLowerCase();\n if (host.includes(':')) return host === '::1' || /^f[cd]/.test(host);\n return (\n !host.includes('.') ||\n /\\.(localhost|local|internal)$/.test(host) ||\n /^(127|10)\\./.test(host) ||\n /^192\\.168\\./.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(host)\n );\n}\n\n/**\n * The OTLP endpoint as it is safe to log, its origin (the path, query or\n * userinfo can carry an API key), and whether spans travel to it in clear\n * text beyond this machine and its private network.\n */\nexport function describeOtelEndpoint(config: OtelConfig): { origin: string; plaintext: boolean } {\n try {\n const url = new URL(config.endpoint || DEFAULT_ENDPOINT);\n return { origin: url.origin, plaintext: url.protocol === 'http:' && !isPrivateHost(url.hostname) };\n } catch {\n return { origin: '(invalid URL)', plaintext: false };\n }\n}\n\n/**\n * Builds a Resource with whichever API the installed `@opentelemetry/resources`\n * provides: 2.x only exports `resourceFromAttributes()` (`Resource` is a type\n * there, so `new Resource()` throws), 1.x exports the `Resource` class.\n */\nexport function createResource(resources: OtelResourcesModule, attributes: Record<string, string>): unknown {\n if (typeof resources?.resourceFromAttributes === 'function') {\n return resources.resourceFromAttributes(attributes);\n }\n if (typeof resources?.Resource === 'function') {\n return new resources.Resource(attributes);\n }\n throw new Error(\n '[iskra/otel] Unsupported @opentelemetry/resources: it exports neither resourceFromAttributes() nor Resource',\n );\n}\n\n/**\n * Initialize the OpenTelemetry NodeSDK with OTLP exporters.\n * Must be called before any drivers start so auto-instrumentation can patch libraries.\n */\nexport async function initOtel(config: OtelConfig, appName: string): Promise<void> {\n if (sdkInstance) return; // Already initialized\n\n try {\n const [\n { NodeSDK },\n { getNodeAutoInstrumentations },\n { OTLPTraceExporter },\n { OTLPMetricExporter },\n { PeriodicExportingMetricReader },\n resources,\n semconv,\n ] = await Promise.all([\n importOptional<OtelSdkNode>(OTEL_MODULES.sdkNode),\n importOptional<OtelAutoInstrumentations>(OTEL_MODULES.autoInstrumentations),\n importOptional<OtelTraceExporter>(OTEL_MODULES.traceExporter),\n importOptional<OtelMetricExporter>(OTEL_MODULES.metricExporter),\n importOptional<OtelSdkMetrics>(OTEL_MODULES.sdkMetrics),\n importOptional<OtelResourcesModule>(OTEL_MODULES.resources),\n importOptional<OtelSemconv>(OTEL_MODULES.semconv),\n ]);\n\n const serviceName = config.serviceName || appName;\n const endpoint = config.endpoint || DEFAULT_ENDPOINT;\n\n const resource = createResource(resources, {\n [semconv.ATTR_SERVICE_NAME ?? 'service.name']: serviceName,\n [semconv.ATTR_SERVICE_VERSION ?? 'service.version']: config.serviceVersion || '0.1.0',\n 'deployment.environment': config.environment || nodeEnv() || 'development',\n ...config.resourceAttributes,\n });\n\n const instrumentationOverrides = autoInstrumentationOptions(config);\n\n const sdk = new NodeSDK({\n resource,\n traceExporter: new OTLPTraceExporter({ url: `${endpoint}/v1/traces` }),\n metricReader: new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ url: `${endpoint}/v1/metrics` }),\n exportIntervalMillis: config.metricIntervalMs ?? 60_000,\n }),\n instrumentations: [getNodeAutoInstrumentations(instrumentationOverrides)],\n });\n\n sdk.start();\n sdkInstance = sdk;\n } catch (err) {\n // Provide a clear error when OTel packages are not installed\n const e = err as { code?: string; message?: string } | null;\n if (\n e?.code === 'ERR_MODULE_NOT_FOUND' ||\n e?.code === 'MODULE_NOT_FOUND' ||\n e?.message?.includes('Cannot find')\n ) {\n throw new Error(\n `[iskra/otel] OpenTelemetry packages are not installed. ` +\n `Install them with: bun add @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node ` +\n `@opentelemetry/exporter-trace-otlp-http @opentelemetry/exporter-metrics-otlp-http ` +\n `@opentelemetry/sdk-metrics @opentelemetry/resources @opentelemetry/semantic-conventions`,\n );\n }\n throw err;\n }\n}\n\n/**\n * Gracefully shut down the OTel SDK, flushing any pending spans/metrics.\n */\nexport async function shutdownOtel(): Promise<void> {\n if (sdkInstance) {\n await sdkInstance.shutdown();\n sdkInstance = null;\n }\n}\n","import { SpanStatusCode, trace, type Attributes, type Span, type Tracer } from '@opentelemetry/api';\n\nexport interface TracedOptions {\n /** Attributes of every span. */\n attributes?: Attributes;\n /** The tracer; default the global provider's `iskra` tracer (no spans without an OpenTelemetry SDK). */\n tracer?: Tracer;\n}\n\nfunction fail(span: Span, error: unknown): void {\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.name : 'Error' });\n}\n\n/**\n * Wraps `fn` in a span named `name`, by convention `layer.domain.method`\n * (`repo.usuarios.buscar`): the span is the active one while `fn` runs, so\n * the spans started inside (a query, a call to another service) are its\n * children; a throw or a rejection marks it as an error. Without an\n * OpenTelemetry SDK it costs a no-op span.\n *\n * ```ts\n * const buscar = traced('repo.usuarios.buscar', async (id: number) => oracle.one(SQL, { id }));\n * ```\n */\nexport function traced<A extends unknown[], R>(\n name: string,\n fn: (...args: A) => R,\n options: TracedOptions = {},\n): (...args: A) => R {\n return function (this: unknown, ...args: A): R {\n const tracer = options.tracer ?? trace.getTracer('iskra');\n return tracer.startActiveSpan(name, { attributes: options.attributes }, (span) => {\n let result: R;\n try {\n result = fn.apply(this, args);\n } catch (error) {\n fail(span, error);\n span.end();\n throw error;\n }\n if (result instanceof Promise) {\n return result.then(\n (value) => {\n span.end();\n return value;\n },\n (error: unknown) => {\n fail(span, error);\n span.end();\n throw error;\n },\n ) as R;\n }\n span.end();\n return result;\n });\n };\n}\n"],"mappings":"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","names":["result"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iskra-bun/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Nucleo del framework Iskra: App, DI, eventos, logger, errores.",
5
5
  "keywords": [
6
6
  "iskra",
@@ -49,6 +49,7 @@
49
49
  "build": "tsup --config ../../tsup.config.ts"
50
50
  },
51
51
  "dependencies": {
52
+ "@opentelemetry/api": "^1.9.0",
52
53
  "c12": "^3.3.4",
53
54
  "mitt": "^3.0.1",
54
55
  "pino": "^9.6.0",
package/src/errors.ts CHANGED
@@ -26,8 +26,14 @@ export const ErrorCodes = {
26
26
  UNAUTHORIZED: 'UNAUTHORIZED',
27
27
  FORBIDDEN: 'FORBIDDEN',
28
28
  NOT_FOUND: 'NOT_FOUND',
29
+ METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',
29
30
  CONFLICT: 'CONFLICT',
31
+ PAYLOAD_TOO_LARGE: 'PAYLOAD_TOO_LARGE',
32
+ UNSUPPORTED_MEDIA_TYPE: 'UNSUPPORTED_MEDIA_TYPE',
30
33
  VALIDATION_ERROR: 'VALIDATION_ERROR',
34
+ RATE_LIMITED: 'RATE_LIMITED',
35
+ SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
36
+ TIMEOUT: 'TIMEOUT',
31
37
 
32
38
  // Data
33
39
  DATABASE_ERROR: 'DATABASE_ERROR',
@@ -50,7 +56,23 @@ export const ErrorCodes = {
50
56
  SOCKET_MESSAGE_ERROR: 'SOCKET_MESSAGE_ERROR',
51
57
  } as const;
52
58
 
53
- export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
59
+ /**
60
+ * Every error code, as keys: Iskra's own (`ErrorCodes`) and the ones an app
61
+ * adds by merging into this interface, which `ErrorCode` then accepts:
62
+ *
63
+ * ```ts
64
+ * declare module '@iskra-bun/core' {
65
+ * interface ErrorCodeRegistry {
66
+ * ORDER_LOCKED: true;
67
+ * }
68
+ * }
69
+ * throw new HttpError(423, 'The order is being edited', { code: 'ORDER_LOCKED' });
70
+ * ```
71
+ */
72
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- extended by declaration merging
73
+ export interface ErrorCodeRegistry extends Record<(typeof ErrorCodes)[keyof typeof ErrorCodes], true> {}
74
+
75
+ export type ErrorCode = keyof ErrorCodeRegistry & string;
54
76
 
55
77
  export interface IskraErrorOptions {
56
78
  code: ErrorCode;
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export * from './types';
3
3
  export * from './logger';
4
4
  export * from './errors';
5
5
  export * from './otel';
6
+ export { traced, type TracedOptions } from './traced';
6
7
  export * from './env';
7
8
  export * from './config/loader';
8
9
  export * from './config/schema';
package/src/traced.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { SpanStatusCode, trace, type Attributes, type Span, type Tracer } from '@opentelemetry/api';
2
+
3
+ export interface TracedOptions {
4
+ /** Attributes of every span. */
5
+ attributes?: Attributes;
6
+ /** The tracer; default the global provider's `iskra` tracer (no spans without an OpenTelemetry SDK). */
7
+ tracer?: Tracer;
8
+ }
9
+
10
+ function fail(span: Span, error: unknown): void {
11
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
12
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.name : 'Error' });
13
+ }
14
+
15
+ /**
16
+ * Wraps `fn` in a span named `name`, by convention `layer.domain.method`
17
+ * (`repo.usuarios.buscar`): the span is the active one while `fn` runs, so
18
+ * the spans started inside (a query, a call to another service) are its
19
+ * children; a throw or a rejection marks it as an error. Without an
20
+ * OpenTelemetry SDK it costs a no-op span.
21
+ *
22
+ * ```ts
23
+ * const buscar = traced('repo.usuarios.buscar', async (id: number) => oracle.one(SQL, { id }));
24
+ * ```
25
+ */
26
+ export function traced<A extends unknown[], R>(
27
+ name: string,
28
+ fn: (...args: A) => R,
29
+ options: TracedOptions = {},
30
+ ): (...args: A) => R {
31
+ return function (this: unknown, ...args: A): R {
32
+ const tracer = options.tracer ?? trace.getTracer('iskra');
33
+ return tracer.startActiveSpan(name, { attributes: options.attributes }, (span) => {
34
+ let result: R;
35
+ try {
36
+ result = fn.apply(this, args);
37
+ } catch (error) {
38
+ fail(span, error);
39
+ span.end();
40
+ throw error;
41
+ }
42
+ if (result instanceof Promise) {
43
+ return result.then(
44
+ (value) => {
45
+ span.end();
46
+ return value;
47
+ },
48
+ (error: unknown) => {
49
+ fail(span, error);
50
+ span.end();
51
+ throw error;
52
+ },
53
+ ) as R;
54
+ }
55
+ span.end();
56
+ return result;
57
+ });
58
+ };
59
+ }