Consolidate error infrastructure into errors.ts per module

Apply the canonical error pattern (docs/conventions.md rule 6) to the
remaining 16 modules: every module's <MOD>_ERR seed, <MOD>_ERR_* codes,
<MOD>_ERROR_MESSAGES catalogue, error classes and is*Error guards now
live in a single errors.ts file. consts.ts retains only configuration
unrelated to errors (event names, default values, op identifiers).

Modules consolidated:
- libs/active-app, libs/auth, libs/bus, libs/cache, libs/dom,
  libs/lang, libs/perm, libs/timer (new errors.ts)
- arts/active-app, arts/auth, arts/connection, arts/http,
  arts/logger, arts/perm, arts/session, arts/sium
- svrs/perm

`<MOD>_ERROR_MESSAGES: ErrorMessages` declared in every module,
indexed by ErrCode. The ErrorMessages type was added to libs/errs/code
in the previous pass; this commit propagates its use everywhere.

`libs/errs/consts.ts` is the documented exception: its codes
(`errs::format_invalid`, `errs::unknown`) live as string literals in
consts.ts because errors.ts imports them and code.ts imports
CodeFormatError from errors.ts — the import graph forbids using
errCode() at that position.

Side cleanups:
- libs/cache: legacy CACHE_ERROR_MESSAGES dict (validation strings)
  renamed to CACHE_VALIDATION_MESSAGES so the canonical
  CACHE_ERROR_MESSAGES (ErrorMessages indexed by ErrCode) is the only
  symbol with that name.
- libs/perm: PERM_MODULE = 'perm' added (it was implicit before).
- arts/auth, arts/perm, arts/active-app: client-only error codes
  (request_failed, disposed, invalid_response, no_context, etc.) live
  in the artifact's errors.ts but reuse the libs <MOD>_ERR seed so
  every code in the family shares the same module prefix.
- libs/timer: errors.ts created (didn't exist) housing the codes that
  were previously in consts.ts; library has no classes (timer error
  classes live in arts/timer/errors.ts and import the codes from
  libs/timer).
- arts/sium: SIUM_ERR seed, codes and three error classes
  (SiumValidationError, SiumAsyncSchemaError,
  SiumDiscriminatedUnionError) consolidated. Classes were previously
  in core/types.ts; that file no longer carries error infrastructure.
  All call sites in arts/sium/core/* and arts/sium/types/* updated
  their imports from `./types`/`../core/types` to the module's
  `errors.ts`.

All 1334 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
dev 5 months ago
parent 7f2577c8da
commit 0b2c4eb30b

@ -1,6 +1,5 @@
export { APP_MODULE } from '$libs/active-app/consts';
import { APP_ERR, APP_MODULE } from '$libs/active-app/consts';
import { errCode, type ErrCode } from '$libs/errs';
import { APP_MODULE } from '$libs/active-app/consts';
export const APP_LOG_MSG_SESSION_CACHE_CLEAR_FAILED =
'session change cache clear failed';
@ -14,8 +13,6 @@ export const APP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED = 'tenant-switched';
export const APP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY = 'connectivity';
export const APP_ORCHESTRATION_TRANSLATOR_DISPOSE = 'dispose';
export const APP_ERR_ALREADY_CREATED: ErrCode = errCode(APP_ERR, 'already_created');
export const APP_ERROR_ALREADY_CREATED_SESSION =
`[${APP_MODULE}] App.createActiveSession() called twice — only one session per App.`;

@ -1,5 +1,17 @@
import { CodeError } from '$libs/errs';
import { APP_ERR_ALREADY_CREATED } from './consts.ts';
import { CodeError, errCode, type ErrCode, type ErrorMessages } from '$libs/errs';
import { APP_ERR } from '$libs/active-app/errors';
// ── Error codes ────────────────────────────────────────────────────────
export const APP_ERR_ALREADY_CREATED: ErrCode = errCode(APP_ERR, 'already_created');
// ── Error messages ─────────────────────────────────────────────────────
export const APP_ERROR_MESSAGES: ErrorMessages = {
[APP_ERR_ALREADY_CREATED]: 'App factory called more than once.'
};
// ── Error classes ──────────────────────────────────────────────────────
export class AappAlreadyCreatedError extends CodeError {
constructor(message: string) {
@ -7,6 +19,8 @@ export class AappAlreadyCreatedError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isAappAlreadyCreatedError(error: unknown): error is AappAlreadyCreatedError {
return error instanceof AappAlreadyCreatedError;
}

@ -1,12 +1,12 @@
import {
AUTH_CLIENT_DIAGNOSTIC_EVENTS,
AUTH_ERR_ADAPTER_FAILED,
AUTH_HEADER_NAMES,
AUTH_METHOD_LIST_DEVICES,
AUTH_ROUTE_PATHS,
AUTH_STORAGE_KEYS
} from './consts.ts';
import { authCacheTagsForIdentity } from '$libs/auth/helpers';
import { AUTH_ERR_ADAPTER_FAILED } from '$libs/auth/errors';
import { AuthInvalidResponseError, isAuthRequestFailedError } from './errors.ts';
import { emitAuthClientDiagnostic, type AuthClientDiagnostics } from './diagnostics.ts';
import type {

@ -1,27 +1,6 @@
export {
AUTH_CONTENT_TYPES,
AUTH_COOKIE_NAMES,
AUTH_ERR,
AUTH_ERR_ACCOUNT_NOT_LINKED,
AUTH_ERR_ADAPTER_FAILED,
AUTH_ERR_ASSURANCE_REQUIRED,
AUTH_ERR_CONFIG_INVALID,
AUTH_ERR_CREDENTIAL_INVALID,
AUTH_ERR_CSRF_INVALID,
AUTH_ERR_FLOW_EXPIRED,
AUTH_ERR_FLOW_INVALID,
AUTH_ERR_IDENTIFIER_TAKEN,
AUTH_ERR_MFA_REQUIRED,
AUTH_ERR_OAUTH_PROVIDER_FAILED,
AUTH_ERR_OAUTH_STATE_INVALID,
AUTH_ERR_OTP_INVALID,
AUTH_ERR_RATE_LIMITED,
AUTH_ERR_ROUTE_NOT_FOUND,
AUTH_ERR_SESSION_REQUIRED,
AUTH_ERR_SESSION_REVOKED,
AUTH_ERR_TENANT_BOUNDARY,
AUTH_ERR_TOKEN_REUSE_DETECTED,
AUTH_ERR_WEBAUTHN_FAILED,
AUTH_EVENT_NAMES,
AUTH_HEADER_NAMES,
AUTH_HTTP_METHODS,
@ -32,19 +11,6 @@ export {
AUTH_STORAGE_KEYS
} from '$libs/auth/consts';
import { AUTH_ERR } from '$libs/auth/consts';
import { errCode, type ErrCode } from '$libs/errs';
// ── Client-only error codes ────────────────────────────────────────────
//
// These three live in `arts/auth` because they describe failures of the
// HTTP-based active client; the libs/auth seed is reused so all auth
// codes share the `'auth::'` prefix.
export const AUTH_ERR_REQUEST_FAILED: ErrCode = errCode(AUTH_ERR, 'request_failed');
export const AUTH_ERR_DISPOSED: ErrCode = errCode(AUTH_ERR, 'disposed');
export const AUTH_ERR_INVALID_RESPONSE: ErrCode = errCode(AUTH_ERR, 'invalid_response');
export const AUTH_CLIENT_DIAGNOSTIC_EVENTS = {
OPERATION_FAILED: 'auth.client.operation_failed',
CACHE_INVALIDATION_FAILED: 'auth.client.cache_invalidation_failed'

@ -1,14 +1,31 @@
import { CodeError } from '$libs/errs';
import { CodeError, errCode, type ErrCode, type ErrorMessages } from '$libs/errs';
import { AUTH_ERR } from '$libs/auth/errors';
import {
AUTH_ERR_DISPOSED,
AUTH_ERR_INVALID_RESPONSE,
AUTH_ERR_REQUEST_FAILED,
AUTH_ERROR_MSG_DISPOSED_SUFFIX,
AUTH_ERROR_MSG_INVALID_RESPONSE_PREFIX,
AUTH_ERROR_MSG_REQUEST_FAILED_PREFIX
} from './consts.ts';
import type { AuthClientSafeError } from '$libs/auth/types';
// ── Error codes (client-side only) ─────────────────────────────────────
//
// These three describe failures of the active HTTP client. The libs/auth
// seed is reused so all auth codes share the `'auth::'` prefix.
export const AUTH_ERR_REQUEST_FAILED: ErrCode = errCode(AUTH_ERR, 'request_failed');
export const AUTH_ERR_DISPOSED: ErrCode = errCode(AUTH_ERR, 'disposed');
export const AUTH_ERR_INVALID_RESPONSE: ErrCode = errCode(AUTH_ERR, 'invalid_response');
// ── Error messages ─────────────────────────────────────────────────────
export const AUTH_CLIENT_ERROR_MESSAGES: ErrorMessages = {
[AUTH_ERR_REQUEST_FAILED]: AUTH_ERROR_MSG_REQUEST_FAILED_PREFIX,
[AUTH_ERR_DISPOSED]: AUTH_ERROR_MSG_DISPOSED_SUFFIX,
[AUTH_ERR_INVALID_RESPONSE]: AUTH_ERROR_MSG_INVALID_RESPONSE_PREFIX
};
// ── Error classes ──────────────────────────────────────────────────────
export class AuthRequestFailedError extends CodeError {
override readonly cause?: unknown;
@ -44,6 +61,8 @@ export class AuthInvalidResponseError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isAuthRequestFailedError(error: unknown): error is AuthRequestFailedError {
return error instanceof AuthRequestFailedError;
}

@ -8,30 +8,6 @@ export {
AUTH_CLIENT_LOG_MESSAGE_CACHE_INVALIDATION_FAILED,
AUTH_CLIENT_LOG_MESSAGE_OPERATION_FAILED,
AUTH_COOKIE_NAMES,
AUTH_ERR,
AUTH_ERR_ACCOUNT_NOT_LINKED,
AUTH_ERR_ADAPTER_FAILED,
AUTH_ERR_ASSURANCE_REQUIRED,
AUTH_ERR_CONFIG_INVALID,
AUTH_ERR_CREDENTIAL_INVALID,
AUTH_ERR_CSRF_INVALID,
AUTH_ERR_DISPOSED,
AUTH_ERR_FLOW_EXPIRED,
AUTH_ERR_FLOW_INVALID,
AUTH_ERR_IDENTIFIER_TAKEN,
AUTH_ERR_INVALID_RESPONSE,
AUTH_ERR_MFA_REQUIRED,
AUTH_ERR_OAUTH_PROVIDER_FAILED,
AUTH_ERR_OAUTH_STATE_INVALID,
AUTH_ERR_OTP_INVALID,
AUTH_ERR_RATE_LIMITED,
AUTH_ERR_REQUEST_FAILED,
AUTH_ERR_ROUTE_NOT_FOUND,
AUTH_ERR_SESSION_REQUIRED,
AUTH_ERR_SESSION_REVOKED,
AUTH_ERR_TENANT_BOUNDARY,
AUTH_ERR_TOKEN_REUSE_DETECTED,
AUTH_ERR_WEBAUTHN_FAILED,
AUTH_ERROR_MSG_DISPOSED_SUFFIX,
AUTH_ERROR_MSG_INVALID_RESPONSE_PREFIX,
AUTH_ERROR_MSG_REQUEST_FAILED_PREFIX,
@ -57,8 +33,36 @@ export {
AUTH_STORAGE_KEYS,
AUTH_MODULE
} from './consts.ts';
export {
AUTH_ERR,
AUTH_ERR_ACCOUNT_NOT_LINKED,
AUTH_ERR_ADAPTER_FAILED,
AUTH_ERR_ASSURANCE_REQUIRED,
AUTH_ERR_CONFIG_INVALID,
AUTH_ERR_CREDENTIAL_INVALID,
AUTH_ERR_CSRF_INVALID,
AUTH_ERR_FLOW_EXPIRED,
AUTH_ERR_FLOW_INVALID,
AUTH_ERR_IDENTIFIER_TAKEN,
AUTH_ERR_MFA_REQUIRED,
AUTH_ERR_OAUTH_PROVIDER_FAILED,
AUTH_ERR_OAUTH_STATE_INVALID,
AUTH_ERR_OTP_INVALID,
AUTH_ERR_RATE_LIMITED,
AUTH_ERR_ROUTE_NOT_FOUND,
AUTH_ERR_SESSION_REQUIRED,
AUTH_ERR_SESSION_REVOKED,
AUTH_ERR_TENANT_BOUNDARY,
AUTH_ERR_TOKEN_REUSE_DETECTED,
AUTH_ERR_WEBAUTHN_FAILED,
AUTH_ERROR_MESSAGES
} from '$libs/auth/errors';
export { createAuthClientDiagnostics, emitAuthClientDiagnostic } from './diagnostics.ts';
export {
AUTH_CLIENT_ERROR_MESSAGES,
AUTH_ERR_DISPOSED,
AUTH_ERR_INVALID_RESPONSE,
AUTH_ERR_REQUEST_FAILED,
AuthDisposedError,
AuthInvalidResponseError,
AuthRequestFailedError,

@ -1,5 +1,3 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const CONNECTION_MODULE = 'connection';
export const CONNECTION_LOGGER_SCOPE_SEPARATOR = ':';
@ -219,17 +217,6 @@ export const CONNECTION_LOG_MSG_BROWSER_RECONNECT = 'browser lifecycle reconnect
export const CONNECTION_ERROR_PREFIX = `[${CONNECTION_MODULE}] `;
// ── Error codes ────────────────────────────────────────────────────────
export const CONNECTION_ERR: ModuleSeed = moduleSeed(CONNECTION_MODULE);
export const CONNECTION_ERR_DISPOSED: ErrCode = errCode(CONNECTION_ERR, 'disposed');
export const CONNECTION_ERR_CONNECTION_ALREADY_EXISTS: ErrCode = errCode(CONNECTION_ERR, 'connection_already_exists');
export const CONNECTION_ERR_CONNECTION_NOT_FOUND: ErrCode = errCode(CONNECTION_ERR, 'connection_not_found');
export const CONNECTION_ERR_INVALID_CONNECTION_NAME: ErrCode = errCode(CONNECTION_ERR, 'invalid_connection_name');
export const CONNECTION_ERR_INVALID_FRAME: ErrCode = errCode(CONNECTION_ERR, 'invalid_frame');
export const CONNECTION_ERR_CHANNEL_ALREADY_EXISTS: ErrCode = errCode(CONNECTION_ERR, 'channel_already_exists');
export const CONNECTION_ERR_CHANNEL_NOT_FOUND: ErrCode = errCode(CONNECTION_ERR, 'channel_not_found');
export const CONNECTION_ERR_WEBSOCKET_UNAVAILABLE: ErrCode = errCode(CONNECTION_ERR, 'websocket_unavailable');
export const CONNECTION_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed connection engine';
export const CONNECTION_ERROR_MSG_ALREADY_EXISTS_PREFIX = 'connection already exists: ';

@ -1,14 +1,57 @@
import { CodeError } from '$libs/errs';
import {
CONNECTION_ERR_CHANNEL_ALREADY_EXISTS,
CONNECTION_ERR_CHANNEL_NOT_FOUND,
CONNECTION_ERR_CONNECTION_ALREADY_EXISTS,
CONNECTION_ERR_CONNECTION_NOT_FOUND,
CONNECTION_ERR_DISPOSED,
CONNECTION_ERR_INVALID_CONNECTION_NAME,
CONNECTION_ERR_INVALID_FRAME,
CONNECTION_ERR_WEBSOCKET_UNAVAILABLE
} from './consts.ts';
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { CONNECTION_MODULE } from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const CONNECTION_ERR: ModuleSeed = moduleSeed(CONNECTION_MODULE);
export const CONNECTION_ERR_DISPOSED: ErrCode = errCode(CONNECTION_ERR, 'disposed');
export const CONNECTION_ERR_CONNECTION_ALREADY_EXISTS: ErrCode = errCode(
CONNECTION_ERR,
'connection_already_exists'
);
export const CONNECTION_ERR_CONNECTION_NOT_FOUND: ErrCode = errCode(
CONNECTION_ERR,
'connection_not_found'
);
export const CONNECTION_ERR_INVALID_CONNECTION_NAME: ErrCode = errCode(
CONNECTION_ERR,
'invalid_connection_name'
);
export const CONNECTION_ERR_INVALID_FRAME: ErrCode = errCode(CONNECTION_ERR, 'invalid_frame');
export const CONNECTION_ERR_CHANNEL_ALREADY_EXISTS: ErrCode = errCode(
CONNECTION_ERR,
'channel_already_exists'
);
export const CONNECTION_ERR_CHANNEL_NOT_FOUND: ErrCode = errCode(
CONNECTION_ERR,
'channel_not_found'
);
export const CONNECTION_ERR_WEBSOCKET_UNAVAILABLE: ErrCode = errCode(
CONNECTION_ERR,
'websocket_unavailable'
);
// ── Error messages ─────────────────────────────────────────────────────
export const CONNECTION_ERROR_MESSAGES: ErrorMessages = {
[CONNECTION_ERR_DISPOSED]: `[${CONNECTION_MODULE}] disposed`,
[CONNECTION_ERR_CONNECTION_ALREADY_EXISTS]: `[${CONNECTION_MODULE}] connection already exists`,
[CONNECTION_ERR_CONNECTION_NOT_FOUND]: `[${CONNECTION_MODULE}] connection not found`,
[CONNECTION_ERR_INVALID_CONNECTION_NAME]: `[${CONNECTION_MODULE}] invalid connection name`,
[CONNECTION_ERR_INVALID_FRAME]: `[${CONNECTION_MODULE}] invalid connection frame`,
[CONNECTION_ERR_CHANNEL_ALREADY_EXISTS]: `[${CONNECTION_MODULE}] channel already exists`,
[CONNECTION_ERR_CHANNEL_NOT_FOUND]: `[${CONNECTION_MODULE}] channel not found`,
[CONNECTION_ERR_WEBSOCKET_UNAVAILABLE]: `[${CONNECTION_MODULE}] WebSocket is not available in this runtime`
};
// ── Error classes ──────────────────────────────────────────────────────
export class ConnectionDisposedError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
@ -66,6 +109,8 @@ export class ConnectionWebSocketUnavailableError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isConnectionDisposedError(value: unknown): value is ConnectionDisposedError {
return value instanceof ConnectionDisposedError;
}
@ -76,9 +121,7 @@ export function isConnectionAlreadyExistsError(
return value instanceof ConnectionAlreadyExistsError;
}
export function isConnectionNotFoundError(
value: unknown
): value is ConnectionNotFoundError {
export function isConnectionNotFoundError(value: unknown): value is ConnectionNotFoundError {
return value instanceof ConnectionNotFoundError;
}
@ -88,7 +131,9 @@ export function isConnectionInvalidNameError(
return value instanceof ConnectionInvalidNameError;
}
export function isConnectionInvalidFrameError(value: unknown): value is ConnectionInvalidFrameError {
export function isConnectionInvalidFrameError(
value: unknown
): value is ConnectionInvalidFrameError {
return value instanceof ConnectionInvalidFrameError;
}

@ -1,4 +1,3 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
import type { HttpMethod, RetryConfig } from './types.ts';
import {
HTTP_DEFAULT_RETRY_STATUSES,
@ -50,14 +49,6 @@ export const HTTP_MAX_ERROR_BODY_BYTES = 10 * 1024 * 1024;
/** Headers from which `Retry-After` semantics are read, in priority order. */
export { HTTP_RETRY_AFTER_HEADERS };
// ── Error codes ────────────────────────────────────────────────────────
export const HTTP_ERR: ModuleSeed = moduleSeed(HTTP_MODULE);
export const HTTP_ERR_NETWORK: ErrCode = errCode(HTTP_ERR, 'network');
export const HTTP_ERR_TIMEOUT: ErrCode = errCode(HTTP_ERR, 'timeout');
export const HTTP_ERR_ABORT: ErrCode = errCode(HTTP_ERR, 'abort');
export const HTTP_ERR_BODY_VALIDATION: ErrCode = errCode(HTTP_ERR, 'body_validation');
export const HTTP_TIMEOUT_SCOPE_ATTEMPT = 'attempt';
export const HTTP_TIMEOUT_SCOPE_TOTAL = 'total';

@ -1,14 +1,37 @@
import { CodeError } from '$libs/errs';
import {
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import type { StandardSchemaV1 } from '$libs/standard-schema';
import {
HTTP_ERR_ABORT,
HTTP_ERR_BODY_VALIDATION,
HTTP_ERR_NETWORK,
HTTP_ERR_TIMEOUT,
HTTP_MODULE,
HTTP_TIMEOUT_SCOPE_ATTEMPT,
HTTP_TIMEOUT_SCOPE_TOTAL
} from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const HTTP_ERR: ModuleSeed = moduleSeed(HTTP_MODULE);
export const HTTP_ERR_NETWORK: ErrCode = errCode(HTTP_ERR, 'network');
export const HTTP_ERR_TIMEOUT: ErrCode = errCode(HTTP_ERR, 'timeout');
export const HTTP_ERR_ABORT: ErrCode = errCode(HTTP_ERR, 'abort');
export const HTTP_ERR_BODY_VALIDATION: ErrCode = errCode(HTTP_ERR, 'body_validation');
// ── Error messages ─────────────────────────────────────────────────────
export const HTTP_ERROR_MESSAGES: ErrorMessages = {
[HTTP_ERR_NETWORK]: `[${HTTP_MODULE}] network error`,
[HTTP_ERR_TIMEOUT]: `[${HTTP_MODULE}] request exceeded timeout`,
[HTTP_ERR_ABORT]: `[${HTTP_MODULE}] request aborted`,
[HTTP_ERR_BODY_VALIDATION]: `[${HTTP_MODULE}] request body failed validation`
};
// ── Error classes ──────────────────────────────────────────────────────
/**
* Hierarchy of error classes raised internally by the engine. Most of them
* surface as the `error` field of a `HttpResult` rather than being thrown —
@ -75,12 +98,12 @@ export class HttpBodyValidationError extends CodeError {
}
}
// ── Type guards ─────────────────────────────────────────────────────────────
// ── Type guards ────────────────────────────────────────────────────────
//
// Compare on `name` rather than `instanceof` so guards stay correct when an
// error crosses a worker boundary (the receiving realm sees a different
// constructor). `CodeError` sets `name` from the code, so name-based guards
// remain stable.
// Compare on `name` rather than `instanceof` so guards stay correct when
// an error crosses a worker boundary (the receiving realm sees a
// different constructor). `CodeError` sets `name` from the code, so
// name-based guards remain stable.
export function isHttpNetworkError(value: unknown): value is HttpNetworkError {
return value instanceof Error && (value as { name?: string }).name === HTTP_ERR_NETWORK;

@ -9,7 +9,7 @@
import { describe, expect, it, vi } from 'vitest';
import { callbackTransport, createEngineLogger, LogLevel } from '$logger';
import type { LogEntry } from '$logger';
import { HTTP_ERR_TIMEOUT } from '../consts.ts';
import { HTTP_ERR_TIMEOUT } from '../errors.ts';
import { createEngineHttp } from '../engine-http.ts';
import { HttpBodyValidationError } from '../errors.ts';
import type { EngineHttpOptions } from '../types.ts';

@ -4,7 +4,7 @@ import {
HTTP_ERR_BODY_VALIDATION,
HTTP_ERR_NETWORK,
HTTP_ERR_TIMEOUT
} from '../consts.ts';
} from '../errors.ts';
import {
HttpAbortError,
HttpBodyValidationError,

@ -1,18 +1,8 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
import { LogLevel } from './types.ts';
import type { LevelsConfig } from './types.ts';
export const LOGGER_MODULE = 'logger';
// ── Error codes ────────────────────────────────────────────────────────
export const LOGGER_ERR: ModuleSeed = moduleSeed(LOGGER_MODULE);
export const LOGGER_ERR_HTTP_TRANSPORT_PUSH_FAILED: ErrCode = errCode(
LOGGER_ERR,
'http_transport_push_failed'
);
export const LOGGER_ERR_LOKI_PUSH_FAILED: ErrCode = errCode(LOGGER_ERR, 'loki_push_failed');
/**
* Runtime identifier of the logging engine. Used for:
* - `[engine_logger]` prefix in console output
@ -107,9 +97,3 @@ export const LEVEL_CONSOLE_METHOD: Readonly<
[LogLevel.FATAL]: 'error'
};
export const LOGGER_ERRORS = {
HTTP_TRANSPORT_PUSH_FAILED: (status: number, statusText: string, body: string): string =>
`http transport push failed: ${status} ${statusText} ${body}`,
LOKI_PUSH_FAILED: (status: number, statusText: string, body: string): string =>
`Loki push failed: ${status} ${statusText} ${body}`
} as const;

@ -1,5 +1,38 @@
import { CodeError } from '$libs/errs';
import { LOGGER_ERR_HTTP_TRANSPORT_PUSH_FAILED, LOGGER_ERR_LOKI_PUSH_FAILED, LOGGER_ERRORS } from './consts.ts';
import {
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { LOGGER_MODULE } from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const LOGGER_ERR: ModuleSeed = moduleSeed(LOGGER_MODULE);
export const LOGGER_ERR_HTTP_TRANSPORT_PUSH_FAILED: ErrCode = errCode(
LOGGER_ERR,
'http_transport_push_failed'
);
export const LOGGER_ERR_LOKI_PUSH_FAILED: ErrCode = errCode(LOGGER_ERR, 'loki_push_failed');
// ── Error messages ─────────────────────────────────────────────────────
export const LOGGER_ERROR_MESSAGES: ErrorMessages = {
[LOGGER_ERR_HTTP_TRANSPORT_PUSH_FAILED]: (
status: number,
statusText: string,
body: string
): string => `[${LOGGER_MODULE}] http transport push failed: ${status} ${statusText} ${body}`,
[LOGGER_ERR_LOKI_PUSH_FAILED]: (
status: number,
statusText: string,
body: string
): string => `[${LOGGER_MODULE}] Loki push failed: ${status} ${statusText} ${body}`
};
// ── Error classes ──────────────────────────────────────────────────────
export class LoggerHttpTransportPushFailedError extends CodeError {
readonly status: number;
@ -7,8 +40,13 @@ export class LoggerHttpTransportPushFailedError extends CodeError {
readonly body: string;
constructor(status: number, statusText: string, body: string) {
const fn = LOGGER_ERROR_MESSAGES[LOGGER_ERR_HTTP_TRANSPORT_PUSH_FAILED] as (
status: number,
statusText: string,
body: string
) => string;
super(LOGGER_ERR_HTTP_TRANSPORT_PUSH_FAILED, {
message: LOGGER_ERRORS.HTTP_TRANSPORT_PUSH_FAILED(status, statusText, body)
message: fn(status, statusText, body)
});
this.status = status;
this.statusText = statusText;
@ -22,8 +60,13 @@ export class LoggerLokiPushFailedError extends CodeError {
readonly body: string;
constructor(status: number, statusText: string, body: string) {
const fn = LOGGER_ERROR_MESSAGES[LOGGER_ERR_LOKI_PUSH_FAILED] as (
status: number,
statusText: string,
body: string
) => string;
super(LOGGER_ERR_LOKI_PUSH_FAILED, {
message: LOGGER_ERRORS.LOKI_PUSH_FAILED(status, statusText, body)
message: fn(status, statusText, body)
});
this.status = status;
this.statusText = statusText;
@ -31,6 +74,8 @@ export class LoggerLokiPushFailedError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isLoggerHttpTransportPushFailedError(
error: unknown
): error is LoggerHttpTransportPushFailedError {

File diff suppressed because it is too large Load Diff

@ -1,6 +1,3 @@
import { errCode, type ErrCode } from '$libs/errs';
import { PERM_ERR } from '$libs/perm';
export {
PERM_CLIENT_PATH_BATCH,
PERM_CLIENT_PATH_CHECK,
@ -86,10 +83,4 @@ export const PERM_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed permissio
export const PERM_ERROR_MSG_CLIENT_ENDPOINT_REQUIRED =
'createActivePerms requires an endpoint';
// ── Error codes ────────────────────────────────────────────────────────
export const PERM_ERR_INVALID_ENDPOINT: ErrCode = errCode(PERM_ERR, 'invalid_endpoint');
export const PERM_ERR_NO_CONTEXT: ErrCode = errCode(PERM_ERR, 'no_context');
export const PERM_ERR_REMOTE_REQUEST: ErrCode = errCode(PERM_ERR, 'remote_request');
export const PERM_ERR_CLIENT_DISPOSED: ErrCode = errCode(PERM_ERR, 'client_disposed');

@ -1,10 +1,23 @@
import { CodeError } from '$libs/errs';
import {
PERM_ERR_CLIENT_DISPOSED,
PERM_ERR_INVALID_ENDPOINT,
PERM_ERR_NO_CONTEXT,
PERM_ERR_REMOTE_REQUEST
} from './consts.ts';
import { CodeError, errCode, type ErrCode, type ErrorMessages } from '$libs/errs';
import { PERM_ERR } from '$libs/perm/errors';
// ── Error codes ────────────────────────────────────────────────────────
export const PERM_ERR_INVALID_ENDPOINT: ErrCode = errCode(PERM_ERR, 'invalid_endpoint');
export const PERM_ERR_NO_CONTEXT: ErrCode = errCode(PERM_ERR, 'no_context');
export const PERM_ERR_REMOTE_REQUEST: ErrCode = errCode(PERM_ERR, 'remote_request');
export const PERM_ERR_CLIENT_DISPOSED: ErrCode = errCode(PERM_ERR, 'client_disposed');
// ── Error messages ─────────────────────────────────────────────────────
export const PERM_CLIENT_ERROR_MESSAGES: ErrorMessages = {
[PERM_ERR_INVALID_ENDPOINT]: 'Permission client requires an endpoint.',
[PERM_ERR_NO_CONTEXT]: 'Permission context is not available.',
[PERM_ERR_REMOTE_REQUEST]: 'Permission remote request failed.',
[PERM_ERR_CLIENT_DISPOSED]: 'Permission client disposed.'
};
// ── Error classes ──────────────────────────────────────────────────────
export class PermInvalidEndpointError extends CodeError {
constructor(message: string) {
@ -35,6 +48,8 @@ export class PermDisposedError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isPermInvalidEndpointError(error: unknown): error is PermInvalidEndpointError {
return error instanceof PermInvalidEndpointError;
}

@ -1,5 +1,3 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
/** Logger category emitted by the engine. */
export const SESSION_MODULE = 'session';
@ -226,12 +224,6 @@ export const SESSION_LOG_MSG_REVOKE_GLOBAL_HANDLER_FAILED =
// (`[http] ...`) so log output and stack traces are searchable.
export const SESSION_ERROR_PREFIX = `[${SESSION_MODULE}] `;
// ── Error codes ────────────────────────────────────────────────────────
export const SESSION_ERR: ModuleSeed = moduleSeed(SESSION_MODULE);
export const SESSION_ERR_DISPOSED: ErrCode = errCode(SESSION_ERR, 'disposed');
export const SESSION_ERR_ALREADY_CREATED: ErrCode = errCode(SESSION_ERR, 'already_created');
export const SESSION_ERR_INVALID_SESSION: ErrCode = errCode(SESSION_ERR, 'invalid_session');
export const SESSION_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed engine';
export const SESSION_ERROR_MSG_ADOPT_SERVER_PREFIX = 'adoptServer() rejected: invariant ';

@ -1,20 +1,41 @@
/**
* Error classes raised by `arts/sess`. The engine surfaces lifecycle
* conditions via `SessionEvent` and tagged result types (`AdoptResult`,
* `RefreshResult`, `RevokeResult`) — exceptions are reserved for
* programmer errors where catching at the call site is the right pattern.
* Error infrastructure for `arts/session`. Codes, message catalogue,
* classes and guards live here in a single file (per the canonical
* pattern in `docs/conventions.md` rule 6).
*
* Each class extends `CodeError` (from `$libs/errs`) and carries an
* `ErrCode` that doubles as the error name for stable test matching
* and serialization.
* The engine surfaces lifecycle conditions via `SessionEvent` and
* tagged result types (`AdoptResult`, `RefreshResult`, `RevokeResult`)
* — exceptions are reserved for programmer errors where catching at
* the call site is the right pattern.
*/
import { CodeError } from '$libs/errs';
import {
SESSION_ERR_ALREADY_CREATED,
SESSION_ERR_DISPOSED,
SESSION_ERR_INVALID_SESSION
} from './consts.ts';
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { SESSION_MODULE } from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const SESSION_ERR: ModuleSeed = moduleSeed(SESSION_MODULE);
export const SESSION_ERR_DISPOSED: ErrCode = errCode(SESSION_ERR, 'disposed');
export const SESSION_ERR_ALREADY_CREATED: ErrCode = errCode(SESSION_ERR, 'already_created');
export const SESSION_ERR_INVALID_SESSION: ErrCode = errCode(SESSION_ERR, 'invalid_session');
// ── Error messages ─────────────────────────────────────────────────────
export const SESSION_ERROR_MESSAGES: ErrorMessages = {
[SESSION_ERR_DISPOSED]: `[${SESSION_MODULE}] operation called on a disposed session engine`,
[SESSION_ERR_ALREADY_CREATED]:
`[${SESSION_MODULE}] App.createActiveSession() called more than once`,
[SESSION_ERR_INVALID_SESSION]: `[${SESSION_MODULE}] adoptServer() received an invalid session`
};
// ── Error classes ──────────────────────────────────────────────────────
/**
* The engine was used after `dispose()`. Mutators throw to surface the
@ -54,7 +75,7 @@ export class SessionInvalidError extends CodeError {
}
}
// ── Type guards ─────────────────────────────────────────────────────────────
// ── Type guards ────────────────────────────────────────────────────────
export function isSessionDisposedError(value: unknown): value is SessionDisposedError {
return value instanceof SessionDisposedError;

@ -11,7 +11,7 @@ import {
SESSION_ERR_ALREADY_CREATED,
SESSION_ERR_DISPOSED,
SESSION_ERR_INVALID_SESSION
} from '../consts.ts';
} from '../errors.ts';
import { CodeError } from '$libs/errs';
describe('SessionDisposedError', () => {

@ -13,7 +13,7 @@
import { getContext, setContext } from 'svelte';
import type { BusEventMap, EngineBus } from '$libs/bus';
import { APP_BUS_CONTEXT_KEY, APP_BUS_ERROR_MSG_NO_CONTEXT } from './consts.ts';
import { APP_BUS_CONTEXT_KEY } from './consts.ts';
import { AappBusNoContextError } from './errors.ts';
const BUS_CONTEXT = Symbol(APP_BUS_CONTEXT_KEY);
@ -27,6 +27,6 @@ export function setBus<TEvents extends BusEventMap = BusEventMap>(
export function getBus<TEvents extends BusEventMap = BusEventMap>(): EngineBus<TEvents> {
const bus = getContext<EngineBus<TEvents> | undefined>(BUS_CONTEXT);
if (!bus) throw new AappBusNoContextError(APP_BUS_ERROR_MSG_NO_CONTEXT);
if (!bus) throw new AappBusNoContextError();
return bus;
}

@ -1,31 +1,14 @@
/**
* Generic constants for `libs/active-app`. Public app-event names and payload
* types live in `./events.ts`; constants here are framework-internal
* (context keys, error codes, runtime gates).
* types live in `./events.ts`; error codes, messages and classes live in
* `./errors.ts`; constants here are framework-internal (context keys,
* runtime gates).
*/
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const APP_MODULE = 'app';
export const APP_BUS_CONTEXT_KEY = 'libs.app.bus';
// ── Error codes ────────────────────────────────────────────────────────
export const APP_ERR: ModuleSeed = moduleSeed(APP_MODULE);
export const APP_ERR_BUS: ErrCode = errCode(APP_ERR, 'bus');
export const APP_ERR_BUS_NO_CONTEXT: ErrCode = errCode(APP_ERR_BUS, 'no_context');
export const APP_ERR_EVENT: ErrCode = errCode(APP_ERR, 'event');
export const APP_ERR_EVENT_INVALID_RUNTIME: ErrCode = errCode(APP_ERR_EVENT, 'invalid_runtime');
export const APP_ERR_EVENT_UNSAFE_PAYLOAD: ErrCode = errCode(APP_ERR_EVENT, 'unsafe_payload');
export const APP_BUS_ERROR_MSG_NO_CONTEXT =
`[${APP_MODULE}] no bus in context — call setBus(App.Bus) in a layout before getBus()`;
export const APP_EVENT_ERROR_MSG_INVALID_RUNTIME =
`[${APP_MODULE}] event cannot fire in this runtime`;
export const APP_EVENT_ERROR_MSG_UNSAFE_PAYLOAD =
`[${APP_MODULE}] app event payload contains a sensitive field`;
export const APP_EVENT_SENSITIVE_KEY_PARTS = [
'authorization',
'cookie',

@ -1,15 +1,35 @@
import { CodeError } from '$libs/errs';
import {
APP_BUS_ERROR_MSG_NO_CONTEXT,
APP_ERR_BUS_NO_CONTEXT,
APP_ERR_EVENT_INVALID_RUNTIME,
APP_ERR_EVENT_UNSAFE_PAYLOAD,
APP_EVENT_ERROR_MSG_INVALID_RUNTIME,
APP_EVENT_ERROR_MSG_UNSAFE_PAYLOAD
} from './consts.ts';
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { APP_MODULE } from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const APP_ERR: ModuleSeed = moduleSeed(APP_MODULE);
export const APP_ERR_BUS: ErrCode = errCode(APP_ERR, 'bus');
export const APP_ERR_BUS_NO_CONTEXT: ErrCode = errCode(APP_ERR_BUS, 'no_context');
export const APP_ERR_EVENT: ErrCode = errCode(APP_ERR, 'event');
export const APP_ERR_EVENT_INVALID_RUNTIME: ErrCode = errCode(APP_ERR_EVENT, 'invalid_runtime');
export const APP_ERR_EVENT_UNSAFE_PAYLOAD: ErrCode = errCode(APP_ERR_EVENT, 'unsafe_payload');
// ── Error messages ─────────────────────────────────────────────────────
export const APP_ERROR_MESSAGES: ErrorMessages = {
[APP_ERR_BUS_NO_CONTEXT]:
`[${APP_MODULE}] no bus in context — call setBus(App.Bus) in a layout before getBus()`,
[APP_ERR_EVENT_INVALID_RUNTIME]: `[${APP_MODULE}] event cannot fire in this runtime`,
[APP_ERR_EVENT_UNSAFE_PAYLOAD]: `[${APP_MODULE}] app event payload contains a sensitive field`
};
// ── Error classes ──────────────────────────────────────────────────────
export class AappBusNoContextError extends CodeError {
constructor(message = APP_BUS_ERROR_MSG_NO_CONTEXT) {
constructor(message = APP_ERROR_MESSAGES[APP_ERR_BUS_NO_CONTEXT] as string) {
super(APP_ERR_BUS_NO_CONTEXT, { message });
}
}
@ -21,7 +41,7 @@ export class AappInvalidEventRuntimeError extends CodeError {
constructor(type: string, allowed: string, where: string) {
super(APP_ERR_EVENT_INVALID_RUNTIME, {
message: `${APP_EVENT_ERROR_MSG_INVALID_RUNTIME} (type=${type}, allowed=${allowed}, where=${where})`
message: `${APP_ERROR_MESSAGES[APP_ERR_EVENT_INVALID_RUNTIME] as string} (type=${type}, allowed=${allowed}, where=${where})`
});
this.type = type;
this.allowed = allowed;
@ -35,13 +55,15 @@ export class AappUnsafeEventPayloadError extends CodeError {
constructor(type: string, path: string) {
super(APP_ERR_EVENT_UNSAFE_PAYLOAD, {
message: `${APP_EVENT_ERROR_MSG_UNSAFE_PAYLOAD} (type=${type}, path=${path})`
message: `${APP_ERROR_MESSAGES[APP_ERR_EVENT_UNSAFE_PAYLOAD] as string} (type=${type}, path=${path})`
});
this.type = type;
this.path = path;
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isAappBusNoContextError(error: unknown): error is AappBusNoContextError {
return error instanceof AappBusNoContextError;
}

@ -1,42 +1,6 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const AUTH_ARTIFACT = 'auth' as const;
export const AUTH_MODULE = AUTH_ARTIFACT;
// ── Error codes ────────────────────────────────────────────────────────
//
// Single source of truth for auth error identity. Each `AuthError`
// subclass passes one of these to `CodeError`, so `error.code` and
// `error.name` are the same canonical `ErrCode` (e.g.
// `'auth::config_invalid'`). The lang path used for i18n derives
// directly from the code via `codeToLangPath()`; no separate
// `messageKey` table is needed.
export const AUTH_ERR: ModuleSeed = moduleSeed(AUTH_MODULE);
export const AUTH_ERR_CONFIG_INVALID: ErrCode = errCode(AUTH_ERR, 'config_invalid');
export const AUTH_ERR_ADAPTER_FAILED: ErrCode = errCode(AUTH_ERR, 'adapter_failed');
export const AUTH_ERR_CSRF_INVALID: ErrCode = errCode(AUTH_ERR, 'csrf_invalid');
export const AUTH_ERR_CREDENTIAL_INVALID: ErrCode = errCode(AUTH_ERR, 'credential_invalid');
export const AUTH_ERR_IDENTIFIER_TAKEN: ErrCode = errCode(AUTH_ERR, 'identifier_taken');
export const AUTH_ERR_ACCOUNT_NOT_LINKED: ErrCode = errCode(AUTH_ERR, 'account_not_linked');
export const AUTH_ERR_SESSION_REQUIRED: ErrCode = errCode(AUTH_ERR, 'session_required');
export const AUTH_ERR_SESSION_REVOKED: ErrCode = errCode(AUTH_ERR, 'session_revoked');
export const AUTH_ERR_ASSURANCE_REQUIRED: ErrCode = errCode(AUTH_ERR, 'assurance_required');
export const AUTH_ERR_FLOW_EXPIRED: ErrCode = errCode(AUTH_ERR, 'flow_expired');
export const AUTH_ERR_FLOW_INVALID: ErrCode = errCode(AUTH_ERR, 'flow_invalid');
export const AUTH_ERR_TOKEN_REUSE_DETECTED: ErrCode = errCode(AUTH_ERR, 'token_reuse_detected');
export const AUTH_ERR_RATE_LIMITED: ErrCode = errCode(AUTH_ERR, 'rate_limited');
export const AUTH_ERR_TENANT_BOUNDARY: ErrCode = errCode(AUTH_ERR, 'tenant_boundary');
export const AUTH_ERR_ROUTE_NOT_FOUND: ErrCode = errCode(AUTH_ERR, 'route_not_found');
export const AUTH_ERR_OAUTH_STATE_INVALID: ErrCode = errCode(AUTH_ERR, 'oauth_state_invalid');
export const AUTH_ERR_OAUTH_PROVIDER_FAILED: ErrCode = errCode(
AUTH_ERR,
'oauth_provider_failed'
);
export const AUTH_ERR_OTP_INVALID: ErrCode = errCode(AUTH_ERR, 'otp_invalid');
export const AUTH_ERR_MFA_REQUIRED: ErrCode = errCode(AUTH_ERR, 'mfa_required');
export const AUTH_ERR_WEBAUTHN_FAILED: ErrCode = errCode(AUTH_ERR, 'webauthn_failed');
export const AUTH_AAPP_KEYS = {
ENGINE: 'auth.engine',
ACTIVE: 'auth.active'

@ -1,27 +1,73 @@
import { CodeError } from '$libs/errs';
import {
AUTH_ERR_ACCOUNT_NOT_LINKED,
AUTH_ERR_ADAPTER_FAILED,
AUTH_ERR_ASSURANCE_REQUIRED,
AUTH_ERR_CONFIG_INVALID,
AUTH_ERR_CREDENTIAL_INVALID,
AUTH_ERR_CSRF_INVALID,
AUTH_ERR_FLOW_EXPIRED,
AUTH_ERR_FLOW_INVALID,
AUTH_ERR_IDENTIFIER_TAKEN,
AUTH_ERR_MFA_REQUIRED,
AUTH_ERR_OAUTH_PROVIDER_FAILED,
AUTH_ERR_OAUTH_STATE_INVALID,
AUTH_ERR_OTP_INVALID,
AUTH_ERR_RATE_LIMITED,
AUTH_ERR_ROUTE_NOT_FOUND,
AUTH_ERR_SESSION_REQUIRED,
AUTH_ERR_SESSION_REVOKED,
AUTH_ERR_TENANT_BOUNDARY,
AUTH_ERR_TOKEN_REUSE_DETECTED,
AUTH_ERR_WEBAUTHN_FAILED
} from './consts';
import type { ErrCode } from '$libs/errs';
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { AUTH_MODULE } from './consts';
// ── Error codes ────────────────────────────────────────────────────────
//
// Single source of truth for auth error identity. Each `AuthError`
// subclass passes one of these to `CodeError`, so `error.code` and
// `error.name` are the same canonical `ErrCode` (e.g.
// `'auth::config_invalid'`). The lang path used for i18n derives
// directly from the code via `codeToLangPath()`; no separate
// `messageKey` table is needed.
export const AUTH_ERR: ModuleSeed = moduleSeed(AUTH_MODULE);
export const AUTH_ERR_CONFIG_INVALID: ErrCode = errCode(AUTH_ERR, 'config_invalid');
export const AUTH_ERR_ADAPTER_FAILED: ErrCode = errCode(AUTH_ERR, 'adapter_failed');
export const AUTH_ERR_CSRF_INVALID: ErrCode = errCode(AUTH_ERR, 'csrf_invalid');
export const AUTH_ERR_CREDENTIAL_INVALID: ErrCode = errCode(AUTH_ERR, 'credential_invalid');
export const AUTH_ERR_IDENTIFIER_TAKEN: ErrCode = errCode(AUTH_ERR, 'identifier_taken');
export const AUTH_ERR_ACCOUNT_NOT_LINKED: ErrCode = errCode(AUTH_ERR, 'account_not_linked');
export const AUTH_ERR_SESSION_REQUIRED: ErrCode = errCode(AUTH_ERR, 'session_required');
export const AUTH_ERR_SESSION_REVOKED: ErrCode = errCode(AUTH_ERR, 'session_revoked');
export const AUTH_ERR_ASSURANCE_REQUIRED: ErrCode = errCode(AUTH_ERR, 'assurance_required');
export const AUTH_ERR_FLOW_EXPIRED: ErrCode = errCode(AUTH_ERR, 'flow_expired');
export const AUTH_ERR_FLOW_INVALID: ErrCode = errCode(AUTH_ERR, 'flow_invalid');
export const AUTH_ERR_TOKEN_REUSE_DETECTED: ErrCode = errCode(AUTH_ERR, 'token_reuse_detected');
export const AUTH_ERR_RATE_LIMITED: ErrCode = errCode(AUTH_ERR, 'rate_limited');
export const AUTH_ERR_TENANT_BOUNDARY: ErrCode = errCode(AUTH_ERR, 'tenant_boundary');
export const AUTH_ERR_ROUTE_NOT_FOUND: ErrCode = errCode(AUTH_ERR, 'route_not_found');
export const AUTH_ERR_OAUTH_STATE_INVALID: ErrCode = errCode(AUTH_ERR, 'oauth_state_invalid');
export const AUTH_ERR_OAUTH_PROVIDER_FAILED: ErrCode = errCode(AUTH_ERR, 'oauth_provider_failed');
export const AUTH_ERR_OTP_INVALID: ErrCode = errCode(AUTH_ERR, 'otp_invalid');
export const AUTH_ERR_MFA_REQUIRED: ErrCode = errCode(AUTH_ERR, 'mfa_required');
export const AUTH_ERR_WEBAUTHN_FAILED: ErrCode = errCode(AUTH_ERR, 'webauthn_failed');
// ── Error messages ─────────────────────────────────────────────────────
//
// The wire-safe error projection (`AuthClientSafeError`) carries only
// the code; the UI builds the i18n key with `codeToLangPath(code)`.
// This catalog provides the dev-facing default messages used when
// constructing the error directly, indexed by `ErrCode`.
export const AUTH_ERROR_MESSAGES: ErrorMessages = {
[AUTH_ERR_CONFIG_INVALID]: AUTH_ERR_CONFIG_INVALID,
[AUTH_ERR_ADAPTER_FAILED]: AUTH_ERR_ADAPTER_FAILED,
[AUTH_ERR_CSRF_INVALID]: AUTH_ERR_CSRF_INVALID,
[AUTH_ERR_CREDENTIAL_INVALID]: AUTH_ERR_CREDENTIAL_INVALID,
[AUTH_ERR_IDENTIFIER_TAKEN]: AUTH_ERR_IDENTIFIER_TAKEN,
[AUTH_ERR_ACCOUNT_NOT_LINKED]: AUTH_ERR_ACCOUNT_NOT_LINKED,
[AUTH_ERR_SESSION_REQUIRED]: AUTH_ERR_SESSION_REQUIRED,
[AUTH_ERR_SESSION_REVOKED]: AUTH_ERR_SESSION_REVOKED,
[AUTH_ERR_ASSURANCE_REQUIRED]: AUTH_ERR_ASSURANCE_REQUIRED,
[AUTH_ERR_FLOW_EXPIRED]: AUTH_ERR_FLOW_EXPIRED,
[AUTH_ERR_FLOW_INVALID]: AUTH_ERR_FLOW_INVALID,
[AUTH_ERR_TOKEN_REUSE_DETECTED]: AUTH_ERR_TOKEN_REUSE_DETECTED,
[AUTH_ERR_RATE_LIMITED]: AUTH_ERR_RATE_LIMITED,
[AUTH_ERR_TENANT_BOUNDARY]: AUTH_ERR_TENANT_BOUNDARY,
[AUTH_ERR_ROUTE_NOT_FOUND]: AUTH_ERR_ROUTE_NOT_FOUND,
[AUTH_ERR_OAUTH_STATE_INVALID]: AUTH_ERR_OAUTH_STATE_INVALID,
[AUTH_ERR_OAUTH_PROVIDER_FAILED]: AUTH_ERR_OAUTH_PROVIDER_FAILED,
[AUTH_ERR_OTP_INVALID]: AUTH_ERR_OTP_INVALID,
[AUTH_ERR_MFA_REQUIRED]: AUTH_ERR_MFA_REQUIRED,
[AUTH_ERR_WEBAUTHN_FAILED]: AUTH_ERR_WEBAUTHN_FAILED
};
/**
* Base auth error. All concrete subclasses below extend `AuthError`,

@ -1,5 +1,3 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const BUS_MODULE = 'bus';
export const BUS_DEFAULT_SOURCE = 'bus';
export const BUS_EVENT_ALL = '*';
@ -29,20 +27,5 @@ export const BUS_DIAGNOSTIC_EVENTS = {
LISTENER_LEAK_WARNING: 'bus.listener.leak_warning'
} as const;
// ── Error codes ────────────────────────────────────────────────────────
export const BUS_ERR: ModuleSeed = moduleSeed(BUS_MODULE);
export const BUS_ERR_DISPOSED: ErrCode = errCode(BUS_ERR, 'disposed');
export const BUS_ERR_AGGREGATE_LISTENER: ErrCode = errCode(BUS_ERR, 'aggregate_listener');
export const BUS_ERR_REENTRANCY_LIMIT: ErrCode = errCode(BUS_ERR, 'reentrancy_limit');
export const BUS_ERR_INVALID_PAYLOAD: ErrCode = errCode(BUS_ERR, 'invalid_payload');
export const BUS_ERROR_MSG_DISPOSED = `[${BUS_MODULE}] operation called on a disposed bus`;
export const BUS_ERROR_MSG_AGGREGATE_LISTENER = `[${BUS_MODULE}] one or more listeners failed`;
export const BUS_ERROR_MSG_REENTRANCY_LIMIT =
`[${BUS_MODULE}] synchronous publish recursion exceeded maxReentrancyDepth`;
export const BUS_ERROR_MSG_INVALID_PAYLOAD =
`[${BUS_MODULE}] payload is not cloneable plain data; use $state.snapshot(...) before publishing reactive state and avoid functions, class instances or DOM nodes`;
export const BUS_LOG_MSG_LISTENER_FAILED = 'bus.listener.failed';
export const BUS_LOG_MSG_LISTENER_LEAK_WARNING = 'bus.listener.leak_warning';

@ -1,18 +1,37 @@
import { CodeError } from '$libs/errs';
import {
BUS_ERR_AGGREGATE_LISTENER,
BUS_ERR_DISPOSED,
BUS_ERR_INVALID_PAYLOAD,
BUS_ERR_REENTRANCY_LIMIT,
BUS_ERROR_MSG_AGGREGATE_LISTENER,
BUS_ERROR_MSG_DISPOSED,
BUS_ERROR_MSG_INVALID_PAYLOAD,
BUS_ERROR_MSG_REENTRANCY_LIMIT
} from './consts.ts';
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { BUS_MODULE } from './consts.ts';
import type { BusEnvelope, BusListenerFailure } from './types.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const BUS_ERR: ModuleSeed = moduleSeed(BUS_MODULE);
export const BUS_ERR_DISPOSED: ErrCode = errCode(BUS_ERR, 'disposed');
export const BUS_ERR_AGGREGATE_LISTENER: ErrCode = errCode(BUS_ERR, 'aggregate_listener');
export const BUS_ERR_REENTRANCY_LIMIT: ErrCode = errCode(BUS_ERR, 'reentrancy_limit');
export const BUS_ERR_INVALID_PAYLOAD: ErrCode = errCode(BUS_ERR, 'invalid_payload');
// ── Error messages ─────────────────────────────────────────────────────
export const BUS_ERROR_MESSAGES: ErrorMessages = {
[BUS_ERR_DISPOSED]: `[${BUS_MODULE}] operation called on a disposed bus`,
[BUS_ERR_AGGREGATE_LISTENER]: `[${BUS_MODULE}] one or more listeners failed`,
[BUS_ERR_REENTRANCY_LIMIT]:
`[${BUS_MODULE}] synchronous publish recursion exceeded maxReentrancyDepth`,
[BUS_ERR_INVALID_PAYLOAD]:
`[${BUS_MODULE}] payload is not cloneable plain data; use $state.snapshot(...) before publishing reactive state and avoid functions, class instances or DOM nodes`
};
// ── Error classes ──────────────────────────────────────────────────────
export class BusDisposedError extends CodeError {
constructor(message = BUS_ERROR_MSG_DISPOSED) {
constructor(message = BUS_ERROR_MESSAGES[BUS_ERR_DISPOSED] as string) {
super(BUS_ERR_DISPOSED, { message });
}
}
@ -22,7 +41,9 @@ export class BusAggregateListenerError extends CodeError {
readonly failures: readonly BusListenerFailure[];
constructor(failures: readonly BusListenerFailure[], envelope: BusEnvelope) {
super(BUS_ERR_AGGREGATE_LISTENER, { message: BUS_ERROR_MSG_AGGREGATE_LISTENER });
super(BUS_ERR_AGGREGATE_LISTENER, {
message: BUS_ERROR_MESSAGES[BUS_ERR_AGGREGATE_LISTENER] as string
});
this.failures = failures;
this.envelope = envelope;
}
@ -35,7 +56,7 @@ export class BusReentrancyLimitError extends CodeError {
constructor(type: string, depth: number, maxDepth: number) {
super(BUS_ERR_REENTRANCY_LIMIT, {
message: `${BUS_ERROR_MSG_REENTRANCY_LIMIT} (type=${type}, depth=${depth}, max=${maxDepth})`
message: `${BUS_ERROR_MESSAGES[BUS_ERR_REENTRANCY_LIMIT] as string} (type=${type}, depth=${depth}, max=${maxDepth})`
});
this.type = type;
this.depth = depth;
@ -47,7 +68,11 @@ export class BusInvalidPayloadError extends CodeError {
readonly type: string;
override readonly cause: unknown;
constructor(type: string, cause: unknown, message = BUS_ERROR_MSG_INVALID_PAYLOAD) {
constructor(
type: string,
cause: unknown,
message = BUS_ERROR_MESSAGES[BUS_ERR_INVALID_PAYLOAD] as string
) {
super(BUS_ERR_INVALID_PAYLOAD, {
message: `${message} (type=${type})`,
cause
@ -57,6 +82,8 @@ export class BusInvalidPayloadError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isBusDisposedError(error: unknown): error is BusDisposedError {
return error instanceof BusDisposedError;
}

@ -18,15 +18,6 @@ export {
BUS_DEFAULT_MAX_REENTRANCY_DEPTH,
BUS_DEFAULT_SOURCE,
BUS_DIAGNOSTIC_EVENTS,
BUS_ERR,
BUS_ERR_AGGREGATE_LISTENER,
BUS_ERR_DISPOSED,
BUS_ERR_INVALID_PAYLOAD,
BUS_ERR_REENTRANCY_LIMIT,
BUS_ERROR_MSG_AGGREGATE_LISTENER,
BUS_ERROR_MSG_DISPOSED,
BUS_ERROR_MSG_INVALID_PAYLOAD,
BUS_ERROR_MSG_REENTRANCY_LIMIT,
BUS_EVENT_ALL,
BUS_ID_PREFIX,
BUS_LISTENER_ERROR_MODE_COLLECT,
@ -40,6 +31,12 @@ export {
} from './consts.ts';
export {
BUS_ERR,
BUS_ERR_AGGREGATE_LISTENER,
BUS_ERR_DISPOSED,
BUS_ERR_INVALID_PAYLOAD,
BUS_ERR_REENTRANCY_LIMIT,
BUS_ERROR_MESSAGES,
BusAggregateListenerError,
BusDisposedError,
BusInvalidPayloadError,

@ -1,14 +1,3 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
// ── Error codes (shared by libs/cache, arts/cache, svrs/cache) ────────────
export const CACHE_ERR: ModuleSeed = moduleSeed('cache');
export const CACHE_ERR_KEY: ErrCode = errCode(CACHE_ERR, 'key');
export const CACHE_ERR_SCOPE: ErrCode = errCode(CACHE_ERR, 'scope');
export const CACHE_ERR_POLICY: ErrCode = errCode(CACHE_ERR, 'policy');
export const CACHE_ERR_DISPOSED: ErrCode = errCode(CACHE_ERR, 'disposed');
export const CACHE_ERR_ACTIVE_ENTRY_DISPOSED: ErrCode = errCode(CACHE_ERR, 'active_entry_disposed');
export const CACHE_DEFAULT_NAMESPACE = 'cache';
export const CACHE_DEFAULT_VERSION = '1';
export const CACHE_DEFAULT_SCHEMA_VERSION = 'default';
@ -207,7 +196,7 @@ export const CACHE_DEFAULT_CACHE_POLICY = {
persist: true
} as const;
export const CACHE_ERROR_MESSAGES = {
export const CACHE_VALIDATION_MESSAGES = {
KEY_MUST_BE_ARRAY: 'Invalid cache key: key must be an array.',
TAG_STRING_EMPTY: 'Invalid cache tag: tag string cannot be empty.',
TAG_SHAPE_INVALID: 'Invalid cache tag: tag must be a string or an object.',

@ -1,5 +1,38 @@
import { CodeError } from '$libs/errs';
import { CACHE_ERR_KEY, CACHE_ERR_POLICY, CACHE_ERR_SCOPE } from './consts.ts';
import {
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
// ── Error codes (shared by libs/cache, arts/cache, svrs/cache) ─────────
export const CACHE_ERR: ModuleSeed = moduleSeed('cache');
export const CACHE_ERR_KEY: ErrCode = errCode(CACHE_ERR, 'key');
export const CACHE_ERR_SCOPE: ErrCode = errCode(CACHE_ERR, 'scope');
export const CACHE_ERR_POLICY: ErrCode = errCode(CACHE_ERR, 'policy');
export const CACHE_ERR_DISPOSED: ErrCode = errCode(CACHE_ERR, 'disposed');
export const CACHE_ERR_ACTIVE_ENTRY_DISPOSED: ErrCode = errCode(CACHE_ERR, 'active_entry_disposed');
// ── Error messages ─────────────────────────────────────────────────────
//
// Cache messages are constructed at the call site from validation
// context (key path, scope mode, policy name). The catalog therefore
// keeps the static fragments and templates that the runtime composes;
// it does NOT duplicate the granular messages already built into
// `CACHE_ERROR_MESSAGES` below.
export const CACHE_ERROR_MESSAGES: ErrorMessages = {
[CACHE_ERR_KEY]: 'Invalid cache key.',
[CACHE_ERR_SCOPE]: 'Invalid cache scope.',
[CACHE_ERR_POLICY]: 'Invalid cache policy.',
[CACHE_ERR_DISPOSED]: 'Cache disposed.',
[CACHE_ERR_ACTIVE_ENTRY_DISPOSED]: 'Active cache entry disposed.'
};
// ── Error classes ──────────────────────────────────────────────────────
export class CacheKeyError extends CodeError {
constructor(message: string) {
@ -19,6 +52,8 @@ export class CachePolicyError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isCacheKeyError(error: unknown): error is CacheKeyError {
return error instanceof CacheKeyError;
}

@ -12,6 +12,13 @@ export {
export { defaultCachePolicies, durationToMs } from './policy.ts';
export { resolveScope } from './scope.ts';
export {
CACHE_ERR,
CACHE_ERR_ACTIVE_ENTRY_DISPOSED,
CACHE_ERR_DISPOSED,
CACHE_ERR_KEY,
CACHE_ERR_POLICY,
CACHE_ERR_SCOPE,
CACHE_ERROR_MESSAGES,
CacheKeyError,
CachePolicyError,
CacheScopeError,

@ -54,7 +54,7 @@ export function stableStringify(value: unknown): string {
export function normalizeKey(key: CacheKey): NormalizedKey {
if (!Array.isArray(key)) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_MUST_BE_ARRAY);
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_MUST_BE_ARRAY);
}
const normalized = normalizeValue(
@ -72,7 +72,7 @@ export function normalizeKey(key: CacheKey): NormalizedKey {
export function normalizeKeyPrefixes(key: CacheKey): NormalizedKey[] {
if (!Array.isArray(key)) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_MUST_BE_ARRAY);
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_MUST_BE_ARRAY);
}
const prefixes: NormalizedKey[] = [];
@ -86,18 +86,18 @@ export function normalizeTag(tag: CacheTagLike): string {
if (typeof tag === 'string') {
const trimmed = tag.trim();
if (!trimmed) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.TAG_STRING_EMPTY);
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.TAG_STRING_EMPTY);
}
return trimmed;
}
if (!tag || typeof tag !== 'object') {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.TAG_SHAPE_INVALID);
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.TAG_SHAPE_INVALID);
}
const type = String(tag.type ?? '').trim();
if (!type) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.TAG_TYPE_REQUIRED);
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.TAG_TYPE_REQUIRED);
}
if (!Object.prototype.hasOwnProperty.call(tag, 'id') || tag.id === undefined) {
@ -122,7 +122,7 @@ function normalizeValue(value: unknown, seen: WeakSet<object>, path: string): No
if (type === 'number') {
if (!Number.isFinite(value)) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_NUMBER_NOT_FINITE(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_NUMBER_NOT_FINITE(path));
}
return value as number;
}
@ -139,19 +139,19 @@ function normalizeValue(value: unknown, seen: WeakSet<object>, path: string): No
}
if (type === 'function') {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_FUNCTION_UNSERIALIZABLE(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_FUNCTION_UNSERIALIZABLE(path));
}
if (type === 'symbol') {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_SYMBOL_UNSERIALIZABLE(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_SYMBOL_UNSERIALIZABLE(path));
}
if (typeof value !== 'object') {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_UNSUPPORTED_VALUE(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_UNSUPPORTED_VALUE(path));
}
if (seen.has(value)) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_CIRCULAR(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_CIRCULAR(path));
}
seen.add(value);
@ -159,7 +159,7 @@ function normalizeValue(value: unknown, seen: WeakSet<object>, path: string): No
try {
if (value instanceof Date) {
if (Number.isNaN(value.getTime())) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_INVALID_DATE(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_INVALID_DATE(path));
}
return {
[CACHE_KEY_TYPE_FIELD]: CACHE_KEY_TYPE_DATE,
@ -212,7 +212,7 @@ function normalizeValue(value: unknown, seen: WeakSet<object>, path: string): No
}
if (!isPlainObject(value)) {
throw new CacheKeyError(CACHE_ERROR_MESSAGES.KEY_OBJECT_NOT_PLAIN(path));
throw new CacheKeyError(CACHE_VALIDATION_MESSAGES.KEY_OBJECT_NOT_PLAIN(path));
}
const out: Record<string, NormalizedJson> = {};

@ -86,7 +86,7 @@ export function resolvePolicy(input: {
if (typeof selected === 'string') {
name = selected;
const named = namedPolicies[selected];
if (!named) throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_UNKNOWN(selected));
if (!named) throw new CachePolicyError(CACHE_VALIDATION_MESSAGES.POLICY_UNKNOWN(selected));
raw = named;
} else {
raw = selected;
@ -109,7 +109,7 @@ export function resolvePolicy(input: {
);
if (freshForMs < 0 || staleForMs < 0 || staleIfErrorForMs < 0 || gcAfterMs < 0) {
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_NEGATIVE);
throw new CachePolicyError(CACHE_VALIDATION_MESSAGES.POLICY_DURATION_NEGATIVE);
}
return {
@ -126,14 +126,14 @@ export function resolvePolicy(input: {
export function durationToMs(value: DurationInput, fieldName = 'duration'): number {
if (typeof value === 'number') {
if (!Number.isFinite(value)) {
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_NOT_FINITE(fieldName));
throw new CachePolicyError(CACHE_VALIDATION_MESSAGES.POLICY_DURATION_NOT_FINITE(fieldName));
}
return value;
}
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(value);
if (!match) {
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_INVALID(fieldName));
throw new CachePolicyError(CACHE_VALIDATION_MESSAGES.POLICY_DURATION_INVALID(fieldName));
}
const amount = Number(match[1]);
@ -151,6 +151,6 @@ export function durationToMs(value: DurationInput, fieldName = 'duration'): numb
case CACHE_DURATION_UNIT_DAY:
return amount * CACHE_MS_DAY;
default:
throw new CachePolicyError(CACHE_ERROR_MESSAGES.POLICY_DURATION_UNIT_INVALID(fieldName));
throw new CachePolicyError(CACHE_VALIDATION_MESSAGES.POLICY_DURATION_UNIT_INVALID(fieldName));
}
}

@ -28,12 +28,12 @@ export async function resolveScope(input: {
if (typeof scope !== 'string') {
if (scope.mode !== CACHE_SCOPE_CUSTOM) {
throw new CacheScopeError(CACHE_ERROR_MESSAGES.SCOPE_CUSTOM_MODE_REQUIRED);
throw new CacheScopeError(CACHE_VALIDATION_MESSAGES.SCOPE_CUSTOM_MODE_REQUIRED);
}
const customValues = cleanValues(scope.values);
if (!Object.keys(customValues).length) {
throw new CacheScopeError(CACHE_ERROR_MESSAGES.SCOPE_CUSTOM_VALUES_REQUIRED);
throw new CacheScopeError(CACHE_VALIDATION_MESSAGES.SCOPE_CUSTOM_VALUES_REQUIRED);
}
const canonical = stableStringify({ mode: CACHE_SCOPE_CUSTOM, values: customValues });
return {
@ -127,7 +127,7 @@ export async function resolveScope(input: {
}
default:
throw new CacheScopeError(CACHE_ERROR_MESSAGES.SCOPE_INVALID_MODE(scope));
throw new CacheScopeError(CACHE_VALIDATION_MESSAGES.SCOPE_INVALID_MODE(scope));
}
}
@ -137,7 +137,7 @@ function requireValue(
mode: string
): asserts value is Exclude<PrimitiveScopeValue, null | undefined> {
if (value === undefined || value === null || value === '') {
throw new CacheScopeError(CACHE_ERROR_MESSAGES.SCOPE_REQUIRED_VALUE(mode, name));
throw new CacheScopeError(CACHE_VALIDATION_MESSAGES.SCOPE_REQUIRED_VALUE(mode, name));
}
}
@ -149,7 +149,7 @@ function cleanValues(
const value = values[key];
if (value === undefined) continue;
if (!['string', 'number', 'boolean'].includes(typeof value) && value !== null) {
throw new CacheScopeError(CACHE_ERROR_MESSAGES.SCOPE_VALUE_INVALID(key));
throw new CacheScopeError(CACHE_VALIDATION_MESSAGES.SCOPE_VALUE_INVALID(key));
}
out[key] = value;
}

@ -1,9 +1 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const DOM_MODULE = 'dom';
// ── Error codes ────────────────────────────────────────────────────────
export const DOM_ERR: ModuleSeed = moduleSeed(DOM_MODULE);
export const DOM_ERR_DOCUMENT_REQUIRED: ErrCode = errCode(DOM_ERR, 'document_required');
export const DOM_ERR_WINDOW_REQUIRED: ErrCode = errCode(DOM_ERR, 'window_required');

@ -1,23 +1,42 @@
import { CodeError } from '$libs/errs';
import { DOM_ERR_DOCUMENT_REQUIRED, DOM_ERR_WINDOW_REQUIRED } from './consts';
import {
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { DOM_MODULE } from './consts';
export const DOM_ERRORS = {
DOCUMENT_REQUIRED: '[dom] getDocument() requires a browser document',
WINDOW_REQUIRED: '[dom] getWindow() requires a browser window'
} as const;
// ── Error codes ────────────────────────────────────────────────────────
export const DOM_ERR: ModuleSeed = moduleSeed(DOM_MODULE);
export const DOM_ERR_DOCUMENT_REQUIRED: ErrCode = errCode(DOM_ERR, 'document_required');
export const DOM_ERR_WINDOW_REQUIRED: ErrCode = errCode(DOM_ERR, 'window_required');
// ── Error messages ─────────────────────────────────────────────────────
export const DOM_ERROR_MESSAGES: ErrorMessages = {
[DOM_ERR_DOCUMENT_REQUIRED]: `[${DOM_MODULE}] getDocument() requires a browser document`,
[DOM_ERR_WINDOW_REQUIRED]: `[${DOM_MODULE}] getWindow() requires a browser window`
};
// ── Error classes ──────────────────────────────────────────────────────
export class DomDocumentRequiredError extends CodeError {
constructor(message = DOM_ERRORS.DOCUMENT_REQUIRED) {
constructor(message = DOM_ERROR_MESSAGES[DOM_ERR_DOCUMENT_REQUIRED] as string) {
super(DOM_ERR_DOCUMENT_REQUIRED, { message });
}
}
export class DomWindowRequiredError extends CodeError {
constructor(message = DOM_ERRORS.WINDOW_REQUIRED) {
constructor(message = DOM_ERROR_MESSAGES[DOM_ERR_WINDOW_REQUIRED] as string) {
super(DOM_ERR_WINDOW_REQUIRED, { message });
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isDomDocumentRequiredError(
error: unknown
): error is DomDocumentRequiredError {

@ -1,16 +1,9 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const LANG_ID_PREFIX = '#?';
export const LANG_ID_FALLBACK_SEPARATOR = '|';
export const LANG_MAX_RESOLVE_DEPTH = 3;
export const LANG_MODULE = 'lang';
export const LANG_MONO_LANG_CATEGORY = 'lang.mono';
// ── Error codes ────────────────────────────────────────────────────────
export const LANG_ERR: ModuleSeed = moduleSeed(LANG_MODULE);
export const LANG_ERR_CIRCULAR_REFERENCE: ErrCode = errCode(LANG_ERR, 'circular_reference');
export const LANG_DIAGNOSTIC_EVENTS = {
KEY_NOT_FOUND: 'lang.key_not_found',
CIRCULAR_REFERENCE: 'lang.circular_reference',

@ -1,23 +1,51 @@
import { CodeError } from '$libs/errs';
import { LANG_ERR_CIRCULAR_REFERENCE, LANG_MODULE } from './consts.ts';
import {
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { LANG_MODULE } from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const LANG_ERR: ModuleSeed = moduleSeed(LANG_MODULE);
export const LANG_ERR_CIRCULAR_REFERENCE: ErrCode = errCode(LANG_ERR, 'circular_reference');
// ── Error messages ─────────────────────────────────────────────────────
export const LANG_ERROR_MESSAGES: ErrorMessages = {
[LANG_ERR_CIRCULAR_REFERENCE]: (path: string): string =>
`[${LANG_MODULE}] Circular reference in "${path}".`
};
// ── Error classes ──────────────────────────────────────────────────────
export class LangCircularReferenceError extends CodeError {
readonly path: string;
constructor(path: string) {
super(LANG_ERR_CIRCULAR_REFERENCE, {
message: `[${LANG_MODULE}] Circular reference in "${path}".`
});
const fn = LANG_ERROR_MESSAGES[LANG_ERR_CIRCULAR_REFERENCE] as (path: string) => string;
super(LANG_ERR_CIRCULAR_REFERENCE, { message: fn(path) });
this.path = path;
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isLangCircularReferenceError(
error: unknown
): error is LangCircularReferenceError {
return error instanceof LangCircularReferenceError;
}
// ── Legacy diagnostic / log message strings ────────────────────────────
//
// These are not thrown errors — they are diagnostic/log messages built
// at the call site by the lang resolver. Kept here next to the typed
// errors so the module's user-facing strings live together.
export const LANG_ERRORS = {
KEY_NOT_FOUND: (path: string): string => `[${LANG_MODULE}] Translation key not found: "${path}"`,

@ -14,8 +14,6 @@
*/
export {
LANG_ERR,
LANG_ERR_CIRCULAR_REFERENCE,
LANG_ID_FALLBACK_SEPARATOR,
LANG_ID_PREFIX,
LANG_DIAGNOSTIC_EVENTS,
@ -33,6 +31,9 @@ export type {
} from './diagnostics.ts';
export {
LANG_ERR,
LANG_ERR_CIRCULAR_REFERENCE,
LANG_ERROR_MESSAGES,
LANG_ERRORS,
LangCircularReferenceError,
isLangCircularReferenceError

@ -1,11 +1,4 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
// ── Error codes ────────────────────────────────────────────────────────
export const PERM_ERR: ModuleSeed = moduleSeed('perm');
export const PERM_ERR_DENIED: ErrCode = errCode(PERM_ERR, 'denied');
export const PERM_ERR_SCHEMA: ErrCode = errCode(PERM_ERR, 'schema');
export const PERM_ERR_RUNTIME: ErrCode = errCode(PERM_ERR, 'runtime');
export const PERM_MODULE = 'perm';
export const PERM_EFFECT_ALLOW = 'allow';
export const PERM_EFFECT_DENY = 'deny';
@ -116,11 +109,6 @@ export const PERM_SQL_FALSE = 'FALSE';
export const PERM_SQL_AND = ' AND ';
export const PERM_SQL_OR = ' OR ';
export const PERM_ERROR_NAME_DENIED = 'PermDeniedError';
export const PERM_ERROR_NAME_SCHEMA = 'PermSchemaError';
export const PERM_ERROR_NAME_RUNTIME = 'PermRuntimeError';
export const PERM_ERROR_MSG_DENIED_PREFIX = 'Perm denied: ';
export const PERM_ERROR_MSG_SCHEMA_REQUIRES_RESOURCE =
'permission schema requires at least one resource';
export const PERM_ERROR_MSG_RESOURCE_REQUIRES_ACTIONS_PREFIX =

@ -1,13 +1,33 @@
import { CodeError } from '$libs/errs';
import {
PERM_EFFECT_DENY,
PERM_ERR_DENIED,
PERM_ERR_RUNTIME,
PERM_ERR_SCHEMA,
PERM_ERROR_MSG_DENIED_PREFIX
} from './consts.ts';
CodeError,
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { PERM_EFFECT_DENY, PERM_MODULE } from './consts.ts';
import type { PermDecision } from './types.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const PERM_ERR: ModuleSeed = moduleSeed(PERM_MODULE);
export const PERM_ERR_DENIED: ErrCode = errCode(PERM_ERR, 'denied');
export const PERM_ERR_SCHEMA: ErrCode = errCode(PERM_ERR, 'schema');
export const PERM_ERR_RUNTIME: ErrCode = errCode(PERM_ERR, 'runtime');
// ── Error messages ─────────────────────────────────────────────────────
export const PERM_ERROR_MESSAGES: ErrorMessages = {
[PERM_ERR_DENIED]: 'Permission denied.',
[PERM_ERR_SCHEMA]: 'Permission schema error.',
[PERM_ERR_RUNTIME]: 'Permission runtime error.'
};
export const PERM_ERROR_MSG_DENIED_PREFIX = 'Perm denied: ';
// ── Error classes ──────────────────────────────────────────────────────
export class PermDeniedError extends CodeError {
readonly decision: PermDecision;
@ -34,6 +54,8 @@ export class PermRuntimeError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isPermDeniedError(error: unknown): error is PermDeniedError {
return error instanceof PermDeniedError;
}

@ -8,20 +8,9 @@
* `arts/timer/consts.ts`.
*/
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
/** Logger category emitted by the engine. */
export const TIMER_MODULE = 'timer';
// ── Error codes ────────────────────────────────────────────────────────
export const TIMER_ERR: ModuleSeed = moduleSeed(TIMER_MODULE);
export const TIMER_ERR_DISPOSED: ErrCode = errCode(TIMER_ERR, 'disposed');
export const TIMER_ERR_INVALID_KEY: ErrCode = errCode(TIMER_ERR, 'invalid_key');
export const TIMER_ERR_DUPLICATE_KEY: ErrCode = errCode(TIMER_ERR, 'duplicate_key');
export const TIMER_ERR_INVALID_DELAY: ErrCode = errCode(TIMER_ERR, 'invalid_delay');
export const TIMER_ERR_INACTIVE_TIMER: ErrCode = errCode(TIMER_ERR, 'inactive_timer');
export const TIMER_DIAGNOSTIC_EVENTS = {
LISTENER_THREW: 'timer.listener_threw',
SCHEDULE_AT_PAST: 'timer.schedule_at_past',

@ -0,0 +1,27 @@
import {
errCode,
moduleSeed,
type ErrCode,
type ErrorMessages,
type ModuleSeed
} from '$libs/errs';
import { TIMER_MODULE } from './consts.ts';
// ── Error codes ────────────────────────────────────────────────────────
export const TIMER_ERR: ModuleSeed = moduleSeed(TIMER_MODULE);
export const TIMER_ERR_DISPOSED: ErrCode = errCode(TIMER_ERR, 'disposed');
export const TIMER_ERR_INVALID_KEY: ErrCode = errCode(TIMER_ERR, 'invalid_key');
export const TIMER_ERR_DUPLICATE_KEY: ErrCode = errCode(TIMER_ERR, 'duplicate_key');
export const TIMER_ERR_INVALID_DELAY: ErrCode = errCode(TIMER_ERR, 'invalid_delay');
export const TIMER_ERR_INACTIVE_TIMER: ErrCode = errCode(TIMER_ERR, 'inactive_timer');
// ── Error messages ─────────────────────────────────────────────────────
export const TIMER_ERROR_MESSAGES: ErrorMessages = {
[TIMER_ERR_DISPOSED]: `[${TIMER_MODULE}] operation called on a disposed timer scheduler`,
[TIMER_ERR_INVALID_KEY]: `[${TIMER_MODULE}] invalid timer key`,
[TIMER_ERR_DUPLICATE_KEY]: `[${TIMER_MODULE}] timer key already scheduled`,
[TIMER_ERR_INVALID_DELAY]: `[${TIMER_MODULE}] invalid timer delay`,
[TIMER_ERR_INACTIVE_TIMER]: `[${TIMER_MODULE}] timer key is not active`
};

@ -1,4 +1,5 @@
export * from './backoff.ts';
export * from './consts.ts';
export * from './debounce.ts';
export * from './errors.ts';
export * from './types.ts';

@ -1,6 +1,11 @@
import {
AUTH_CONTENT_TYPES,
AUTH_COOKIE_NAMES,
AUTH_HEADER_NAMES,
AUTH_HTTP_STATUS
} from '$libs/auth/consts';
import { parseAuthCookieHeader, serializeAuthSetCookie } from '$libs/auth/cookies';
import {
AUTH_ERR_ASSURANCE_REQUIRED,
AUTH_ERR_CSRF_INVALID,
AUTH_ERR_RATE_LIMITED,
@ -9,11 +14,8 @@ import {
AUTH_ERR_SESSION_REVOKED,
AUTH_ERR_TENANT_BOUNDARY,
AUTH_ERR_TOKEN_REUSE_DETECTED,
AUTH_HEADER_NAMES,
AUTH_HTTP_STATUS
} from '$libs/auth/consts';
import { parseAuthCookieHeader, serializeAuthSetCookie } from '$libs/auth/cookies';
import { toAuthSafeError } from '$libs/auth/errors';
toAuthSafeError
} from '$libs/auth/errors';
import type {
AuthCsrfIssueInput,
AuthCurrentInput,

@ -1,6 +1,3 @@
import { errCode, type ErrCode } from '$libs/errs';
import { PERM_ERR } from '$libs/perm';
export { PERM_MODULE } from '$libs/svrs/perm';
export const PERM_DIAGNOSTIC_EVENTS = {
@ -16,11 +13,6 @@ export const PERM_LOG_MSG_INDETERMINATE = 'authorization indeterminate';
export const PERM_ERROR_MSG_BODY_MUST_BE_OBJECT = 'Request body must be an object';
export const PERM_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed permissions engine';
// ── Error codes ────────────────────────────────────────────────────────
export const PERM_ERR_INVALID_BODY: ErrCode = errCode(PERM_ERR, 'invalid_body');
export const PERM_ERR_SERVER_DISPOSED: ErrCode = errCode(PERM_ERR, 'server_disposed');
export const PERM_METHOD_CHECK = 'perm.check';
export const PERM_METHOD_CAN = 'perm.can';
export const PERM_METHOD_ASSERT = 'perm.assert';

@ -1,5 +1,19 @@
import { CodeError } from '$libs/errs';
import { PERM_ERR_INVALID_BODY, PERM_ERR_SERVER_DISPOSED } from './consts.ts';
import { CodeError, errCode, type ErrCode, type ErrorMessages } from '$libs/errs';
import { PERM_ERR } from '$libs/perm/errors';
// ── Error codes ────────────────────────────────────────────────────────
export const PERM_ERR_INVALID_BODY: ErrCode = errCode(PERM_ERR, 'invalid_body');
export const PERM_ERR_SERVER_DISPOSED: ErrCode = errCode(PERM_ERR, 'server_disposed');
// ── Error messages ─────────────────────────────────────────────────────
export const PERM_SERVER_ERROR_MESSAGES: ErrorMessages = {
[PERM_ERR_INVALID_BODY]: 'Request body must be an object.',
[PERM_ERR_SERVER_DISPOSED]: 'Permission engine disposed.'
};
// ── Error classes ──────────────────────────────────────────────────────
export class PermInvalidBodyError extends CodeError {
constructor(message: string) {
@ -13,6 +27,8 @@ export class PermDisposedError extends CodeError {
}
}
// ── Type guards ────────────────────────────────────────────────────────
export function isPermInvalidBodyError(error: unknown): error is PermInvalidBodyError {
return error instanceof PermInvalidBodyError;
}

Loading…
Cancel
Save

Powered by TurnKey Linux.