Migrate buss/conn/sess/perm/cach errors to CodeError

Continues phase 4 — sweeps the next batch of modules onto the
canonical CodeError base. Same mechanical pattern as sium:

- Add `<MOD>_ERR` module seed and `<MOD>_ERR_*` codes via
  `moduleSeed` + `errCode`.
- Convert error classes to `extends CodeError`. Drop the hardcoded
  `this.name = 'XxxError'` literals — `name` derives from the code.
- Drop the `<MOD>_ERROR_NAME_*` constants that only existed to feed
  those literals.
- Type guards keep using `instanceof Class` (works through the
  CodeError prototype chain).
- Subclass-specific fields (envelope, failures, depth, type, key,
  channel, connection, decision, invariant, etc.) preserved.

Modules migrated this commit:

- libs/buss (4 classes): BusDisposedError, BusAggregateListenerError,
  BusReentrancyLimitError, BusInvalidPayloadError
- arts/timr (5 classes): TimrDisposedError, TimrInvalidKeyError,
  TimrDuplicateKeyError, TimrInvalidDelayError, TimrInactiveTimerError
  (codes live in libs/timers; classes stay in arts/timr)
- arts/conn (8 classes): ConnDisposedError, ConnConnectionAlreadyExistsError,
  ConnConnectionNotFoundError, ConnInvalidConnectionNameError,
  ConnInvalidFrameError, ConnChannelAlreadyExistsError,
  ConnChannelNotFoundError, ConnWebSocketUnavailableError. Adds the
  two missing guards flagged by audit-1-5 section 3.2
  (isConnChannelAlreadyExistsError, isConnChannelNotFoundError).
- arts/sess (3 classes): SessDisposedError, SessAlreadyCreatedError,
  SessInvalidSessionError. Sess test updated to assert against the
  new code-based name.
- libs/perm + arts/perm + svrs/perm (3+4+2 classes): PermissionDeniedError,
  PermissionSchemaError, PermissionRuntimeError, PermInvalidEndpointError,
  PermNoContextError, PermRemoteRequestError, PermDisposedError (×2),
  PermInvalidBodyError. Codes live in libs/perm; arts and svrs reach
  into them.
- libs/cach + arts/cach + svrs/cach (3+1+1 classes): CacheKeyError,
  CacheScopeError, CachePolicyError, CachActiveEntryDisposedError,
  CachDisposedError. Codes consolidated in libs/cach.

Plus audit-1-5 section 3.2 cleanup outside the migration:

- libs/auth/errors.ts: adds the 10 missing type guards
  (isAuthAccountNotLinkedError, isAuthSessionRevokedError,
  isAuthAssuranceRequiredError, isAuthRateLimitedError,
  isAuthTenantBoundaryError, isAuthOAuthStateInvalidError,
  isAuthOAuthProviderError, isAuthOtpInvalidError,
  isAuthMfaRequiredError, isAuthWebAuthnError). Auth's own
  `code`/`messageKey` shape is preserved for now — full migration
  to CodeError needs more thought because of the existing
  AUTH_ERROR_CODES/AUTH_SAFE_MESSAGES dual structure.

Verification: svelte-check 1403 files / 0 errors. Server 1315 tests,
client 19 tests — all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
dev 5 months ago
parent a849d00bff
commit 742c0118b8

@ -2,7 +2,6 @@ import { CACH_MODULE } from '$svrs/cach';
export {
CACH_ERROR_MSG_DISPOSED_SUFFIX,
CACH_ERROR_NAME_DISPOSED,
CACH_LOG_MESSAGE_ADAPTER_ERROR,
CACH_LOG_MESSAGE_BY_EVENT,
CACH_LOG_MESSAGE_DELETE,
@ -56,7 +55,6 @@ export const CACH_ACTIVE_ENTRY_EVENT_LOAD = 'cach.entry.load';
export const CACH_ACTIVE_ENTRY_EVENT_REFRESH = 'cach.entry.refresh';
export const CACH_ACTIVE_ENTRY_EVENT_INVALIDATE = 'cach.entry.invalidate';
export const CACH_ACTIVE_ENTRY_EVENT_SET = 'cach.entry.set';
export const CACH_ERROR_NAME_ACTIVE_ENTRY_DISPOSED = 'CachActiveEntryDisposedError';
export const CACH_ACTIVE_DIAGNOSTIC_EVENTS = {
OPERATION_FAILED: 'cach.active.operation_failed'

@ -1,15 +1,12 @@
import {
CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED,
CACH_ERROR_NAME_ACTIVE_ENTRY_DISPOSED
} from './consts.ts';
import { CodeError } from '$libs/errs';
import { CACH_ERR_ACTIVE_ENTRY_DISPOSED } from '$libs/cach';
import { CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED } from './consts.ts';
export { CachDisposedError, isCachDisposedError } from '$svrs/cach';
export class CachActiveEntryDisposedError extends Error {
override readonly name = CACH_ERROR_NAME_ACTIVE_ENTRY_DISPOSED;
export class CachActiveEntryDisposedError extends CodeError {
constructor(message = CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED) {
super(message);
super(CACH_ERR_ACTIVE_ENTRY_DISPOSED, { message });
}
}

@ -1,4 +1,5 @@
export { createActiveCache } from './active-cache.svelte.ts';
export { CACH_ERR, CACH_ERR_ACTIVE_ENTRY_DISPOSED, CACH_ERR_DISPOSED } from '$libs/cach';
export {
CACH_ACTIVE_ENTRY_EVENT_INVALIDATE,
CACH_ACTIVE_ENTRY_EVENT_LOAD,
@ -21,8 +22,6 @@ export {
CACH_AUTO_INVALIDATE_USER_IDENTITY_CHANGE,
CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED,
CACH_ERROR_MSG_DISPOSED_SUFFIX,
CACH_ERROR_NAME_ACTIVE_ENTRY_DISPOSED,
CACH_ERROR_NAME_DISPOSED,
CACH_LOG_MESSAGE_ADAPTER_ERROR,
CACH_LOG_MESSAGE_BY_EVENT,
CACH_LOG_MESSAGE_DELETE,

@ -1,3 +1,5 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const CONN_MODULE = 'conn';
export const CONN_LOGGER_SCOPE_SEPARATOR = ':';
@ -216,14 +218,18 @@ export const CONN_LOG_MSG_SESSION_REFRESHED = 'session refreshed; reauthenticati
export const CONN_LOG_MSG_BROWSER_RECONNECT = 'browser lifecycle reconnect requested';
export const CONN_ERROR_PREFIX = `[${CONN_MODULE}] `;
export const CONN_ERROR_NAME_DISPOSED = 'ConnDisposedError';
export const CONN_ERROR_NAME_ALREADY_EXISTS = 'ConnConnectionAlreadyExistsError';
export const CONN_ERROR_NAME_NOT_FOUND = 'ConnConnectionNotFoundError';
export const CONN_ERROR_NAME_INVALID_NAME = 'ConnInvalidConnectionNameError';
export const CONN_ERROR_NAME_INVALID_FRAME = 'ConnInvalidFrameError';
export const CONN_ERROR_NAME_CHANNEL_ALREADY_EXISTS = 'ConnChannelAlreadyExistsError';
export const CONN_ERROR_NAME_CHANNEL_NOT_FOUND = 'ConnChannelNotFoundError';
export const CONN_ERROR_NAME_WEBSOCKET_UNAVAILABLE = 'ConnWebSocketUnavailableError';
// ── Error codes ────────────────────────────────────────────────────────
export const CONN_ERR: ModuleSeed = moduleSeed(CONN_MODULE);
export const CONN_ERR_DISPOSED: ErrCode = errCode(CONN_ERR, 'disposed');
export const CONN_ERR_CONNECTION_ALREADY_EXISTS: ErrCode = errCode(CONN_ERR, 'connection_already_exists');
export const CONN_ERR_CONNECTION_NOT_FOUND: ErrCode = errCode(CONN_ERR, 'connection_not_found');
export const CONN_ERR_INVALID_CONNECTION_NAME: ErrCode = errCode(CONN_ERR, 'invalid_connection_name');
export const CONN_ERR_INVALID_FRAME: ErrCode = errCode(CONN_ERR, 'invalid_frame');
export const CONN_ERR_CHANNEL_ALREADY_EXISTS: ErrCode = errCode(CONN_ERR, 'channel_already_exists');
export const CONN_ERR_CHANNEL_NOT_FOUND: ErrCode = errCode(CONN_ERR, 'channel_not_found');
export const CONN_ERR_WEBSOCKET_UNAVAILABLE: ErrCode = errCode(CONN_ERR, 'websocket_unavailable');
export const CONN_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed connection engine';
export const CONN_ERROR_MSG_ALREADY_EXISTS_PREFIX = 'connection already exists: ';

@ -1,103 +1,111 @@
import { CodeError } from '$libs/errs';
import {
CONN_ERROR_NAME_ALREADY_EXISTS,
CONN_ERROR_NAME_CHANNEL_ALREADY_EXISTS,
CONN_ERROR_NAME_CHANNEL_NOT_FOUND,
CONN_ERROR_NAME_DISPOSED,
CONN_ERROR_NAME_INVALID_FRAME,
CONN_ERROR_NAME_INVALID_NAME,
CONN_ERROR_NAME_NOT_FOUND,
CONN_ERROR_NAME_WEBSOCKET_UNAVAILABLE
CONN_ERR_CHANNEL_ALREADY_EXISTS,
CONN_ERR_CHANNEL_NOT_FOUND,
CONN_ERR_CONNECTION_ALREADY_EXISTS,
CONN_ERR_CONNECTION_NOT_FOUND,
CONN_ERR_DISPOSED,
CONN_ERR_INVALID_CONNECTION_NAME,
CONN_ERR_INVALID_FRAME,
CONN_ERR_WEBSOCKET_UNAVAILABLE
} from './consts.ts';
abstract class ConnEngineError extends Error {
abstract readonly name: string;
export class ConnDisposedError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(message, options);
super(CONN_ERR_DISPOSED, { message, cause: options?.cause });
}
}
export class ConnDisposedError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_DISPOSED;
}
export class ConnConnectionAlreadyExistsError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_ALREADY_EXISTS;
export class ConnConnectionAlreadyExistsError extends CodeError {
readonly connection: string;
constructor(message: string, connection: string) {
super(message);
super(CONN_ERR_CONNECTION_ALREADY_EXISTS, { message });
this.connection = connection;
}
}
export class ConnConnectionNotFoundError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_NOT_FOUND;
export class ConnConnectionNotFoundError extends CodeError {
readonly connection: string;
constructor(message: string, connection: string) {
super(message);
super(CONN_ERR_CONNECTION_NOT_FOUND, { message });
this.connection = connection;
}
}
export class ConnInvalidConnectionNameError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_INVALID_NAME;
export class ConnInvalidConnectionNameError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(CONN_ERR_INVALID_CONNECTION_NAME, { message, cause: options?.cause });
}
}
export class ConnInvalidFrameError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_INVALID_FRAME;
export class ConnInvalidFrameError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(CONN_ERR_INVALID_FRAME, { message, cause: options?.cause });
}
}
export class ConnChannelAlreadyExistsError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_CHANNEL_ALREADY_EXISTS;
export class ConnChannelAlreadyExistsError extends CodeError {
readonly channel: string;
constructor(message: string, channel: string) {
super(message);
super(CONN_ERR_CHANNEL_ALREADY_EXISTS, { message });
this.channel = channel;
}
}
export class ConnChannelNotFoundError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_CHANNEL_NOT_FOUND;
export class ConnChannelNotFoundError extends CodeError {
readonly channel: string;
constructor(message: string, channel: string) {
super(message);
super(CONN_ERR_CHANNEL_NOT_FOUND, { message });
this.channel = channel;
}
}
export class ConnWebSocketUnavailableError extends ConnEngineError {
readonly name = CONN_ERROR_NAME_WEBSOCKET_UNAVAILABLE;
export class ConnWebSocketUnavailableError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(CONN_ERR_WEBSOCKET_UNAVAILABLE, { message, cause: options?.cause });
}
}
export function isConnDisposedError(value: unknown): value is ConnDisposedError {
return value instanceof Error && (value as { name?: string }).name === CONN_ERROR_NAME_DISPOSED;
return value instanceof ConnDisposedError;
}
export function isConnConnectionAlreadyExistsError(
value: unknown
): value is ConnConnectionAlreadyExistsError {
return value instanceof Error && (value as { name?: string }).name === CONN_ERROR_NAME_ALREADY_EXISTS;
return value instanceof ConnConnectionAlreadyExistsError;
}
export function isConnConnectionNotFoundError(
value: unknown
): value is ConnConnectionNotFoundError {
return value instanceof Error && (value as { name?: string }).name === CONN_ERROR_NAME_NOT_FOUND;
return value instanceof ConnConnectionNotFoundError;
}
export function isConnInvalidConnectionNameError(
value: unknown
): value is ConnInvalidConnectionNameError {
return value instanceof Error && (value as { name?: string }).name === CONN_ERROR_NAME_INVALID_NAME;
return value instanceof ConnInvalidConnectionNameError;
}
export function isConnInvalidFrameError(value: unknown): value is ConnInvalidFrameError {
return value instanceof Error && (value as { name?: string }).name === CONN_ERROR_NAME_INVALID_FRAME;
return value instanceof ConnInvalidFrameError;
}
export function isConnChannelAlreadyExistsError(
value: unknown
): value is ConnChannelAlreadyExistsError {
return value instanceof ConnChannelAlreadyExistsError;
}
export function isConnChannelNotFoundError(
value: unknown
): value is ConnChannelNotFoundError {
return value instanceof ConnChannelNotFoundError;
}
export function isConnWebSocketUnavailableError(
value: unknown
): value is ConnWebSocketUnavailableError {
return (
value instanceof Error && (value as { name?: string }).name === CONN_ERROR_NAME_WEBSOCKET_UNAVAILABLE
);
return value instanceof ConnWebSocketUnavailableError;
}

@ -1,3 +1,6 @@
import { errCode, type ErrCode } from '$libs/errs';
import { PERM_ERR } from '$libs/perm';
export {
PERM_CLIENT_PATH_BATCH,
PERM_CLIENT_PATH_CHECK,
@ -83,8 +86,10 @@ export const PERM_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed permissio
export const PERM_ERROR_MSG_CLIENT_ENDPOINT_REQUIRED =
'createActivePermissions requires an endpoint';
export const PERM_ERROR_NAME_INVALID_ENDPOINT = 'PermInvalidEndpointError';
export const PERM_ERROR_NAME_NO_CONTEXT = 'PermNoContextError';
export const PERM_ERROR_NAME_REMOTE_REQUEST = 'PermRemoteRequestError';
export const PERM_ERROR_NAME_DISPOSED = 'PermDisposedError';
// ── 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,32 +1,38 @@
import { CodeError } from '$libs/errs';
import {
PERM_ERROR_NAME_INVALID_ENDPOINT,
PERM_ERROR_NAME_NO_CONTEXT,
PERM_ERROR_NAME_REMOTE_REQUEST,
PERM_ERROR_NAME_DISPOSED
PERM_ERR_CLIENT_DISPOSED,
PERM_ERR_INVALID_ENDPOINT,
PERM_ERR_NO_CONTEXT,
PERM_ERR_REMOTE_REQUEST
} from './consts.ts';
export class PermInvalidEndpointError extends Error {
override readonly name = PERM_ERROR_NAME_INVALID_ENDPOINT;
export class PermInvalidEndpointError extends CodeError {
constructor(message: string) {
super(PERM_ERR_INVALID_ENDPOINT, { message });
}
}
export class PermNoContextError extends Error {
override readonly name = PERM_ERROR_NAME_NO_CONTEXT;
export class PermNoContextError extends CodeError {
constructor(message: string) {
super(PERM_ERR_NO_CONTEXT, { message });
}
}
export class PermRemoteRequestError extends Error {
override readonly name = PERM_ERROR_NAME_REMOTE_REQUEST;
export class PermRemoteRequestError extends CodeError {
readonly url?: string;
readonly status?: number;
constructor(
message: string,
readonly url?: string,
readonly status?: number
) {
super(message);
constructor(message: string, url?: string, status?: number) {
super(PERM_ERR_REMOTE_REQUEST, { message });
this.url = url;
this.status = status;
}
}
export class PermDisposedError extends Error {
override readonly name = PERM_ERROR_NAME_DISPOSED;
export class PermDisposedError extends CodeError {
constructor(message: string) {
super(PERM_ERR_CLIENT_DISPOSED, { message });
}
}
export function isPermInvalidEndpointError(error: unknown): error is PermInvalidEndpointError {

@ -36,10 +36,10 @@ export {
PERM_ERROR_MSG_NO_CONTEXT,
PERM_ERROR_MSG_REQUEST_FAILED_PREFIX,
PERM_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX,
PERM_ERROR_NAME_DISPOSED,
PERM_ERROR_NAME_INVALID_ENDPOINT,
PERM_ERROR_NAME_NO_CONTEXT,
PERM_ERROR_NAME_REMOTE_REQUEST,
PERM_ERR_CLIENT_DISPOSED,
PERM_ERR_INVALID_ENDPOINT,
PERM_ERR_NO_CONTEXT,
PERM_ERR_REMOTE_REQUEST,
PERM_HTTP_CREDENTIALS_INCLUDE,
PERM_HTTP_ERROR_METHOD_NOT_ALLOWED,
PERM_HTTP_ERROR_METHOD_NOT_ALLOWED_MESSAGE,

@ -1,3 +1,5 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
/** Logger category emitted by the engine. */
export const SESS_MODULE = 'sess';
@ -224,9 +226,12 @@ export const SESS_LOG_MSG_REVOKE_GLOBAL_HANDLER_FAILED =
// (`[http] ...`) so log output and stack traces are searchable.
export const SESS_ERROR_PREFIX = `[${SESS_MODULE}] `;
export const SESS_ERROR_NAME_DISPOSED = 'SessDisposedError';
export const SESS_ERROR_NAME_ALREADY_CREATED = 'SessAlreadyCreatedError';
export const SESS_ERROR_NAME_INVALID_SESSION = 'SessInvalidSessionError';
// ── Error codes ────────────────────────────────────────────────────────
export const SESS_ERR: ModuleSeed = moduleSeed(SESS_MODULE);
export const SESS_ERR_DISPOSED: ErrCode = errCode(SESS_ERR, 'disposed');
export const SESS_ERR_ALREADY_CREATED: ErrCode = errCode(SESS_ERR, 'already_created');
export const SESS_ERR_INVALID_SESSION: ErrCode = errCode(SESS_ERR, 'invalid_session');
export const SESS_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed engine';
export const SESS_ERROR_MSG_ADOPT_SERVER_PREFIX = 'adoptServer() rejected: invariant ';

@ -4,30 +4,27 @@
* `RefreshResult`, `RevokeResult`) — exceptions are reserved for
* programmer errors where catching at the call site is the right pattern.
*
* Each class carries a literal `name` so it survives serialization across
* worker boundaries and is stable to test against.
* Each class extends `CodeError` (from `$libs/errs`) and carries an
* `ErrCode` that doubles as the error name for stable test matching
* and serialization.
*/
import { CodeError } from '$libs/errs';
import {
SESS_ERROR_NAME_ALREADY_CREATED,
SESS_ERROR_NAME_DISPOSED,
SESS_ERROR_NAME_INVALID_SESSION
SESS_ERR_ALREADY_CREATED,
SESS_ERR_DISPOSED,
SESS_ERR_INVALID_SESSION
} from './consts.ts';
abstract class SessEngineError extends Error {
abstract readonly name: string;
constructor(message: string, options?: { cause?: unknown }) {
super(message, options);
}
}
/**
* The engine was used after `dispose()`. Mutators throw to surface the
* lifecycle bug; reads return `null`-like values so defensive cleanup
* paths don't cascade.
*/
export class SessDisposedError extends SessEngineError {
readonly name = SESS_ERROR_NAME_DISPOSED;
export class SessDisposedError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(SESS_ERR_DISPOSED, { message, cause: options?.cause });
}
}
/**
@ -35,8 +32,10 @@ export class SessDisposedError extends SessEngineError {
* supports a single session per App; multi-account scenarios are out of
* scope (would compose multiple App instances).
*/
export class SessAlreadyCreatedError extends SessEngineError {
readonly name = SESS_ERROR_NAME_ALREADY_CREATED;
export class SessAlreadyCreatedError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(SESS_ERR_ALREADY_CREATED, { message, cause: options?.cause });
}
}
/**
@ -47,11 +46,10 @@ export class SessAlreadyCreatedError extends SessEngineError {
* condition, so they are surfaced as an exception instead of being
* silently accepted.
*/
export class SessInvalidSessionError extends SessEngineError {
readonly name = SESS_ERROR_NAME_INVALID_SESSION;
export class SessInvalidSessionError extends CodeError {
readonly invariant: string;
constructor(invariant: string, message: string) {
super(message);
super(SESS_ERR_INVALID_SESSION, { message });
this.invariant = invariant;
}
}
@ -59,13 +57,13 @@ export class SessInvalidSessionError extends SessEngineError {
// ── Type guards ─────────────────────────────────────────────────────────────
export function isSessDisposedError(value: unknown): value is SessDisposedError {
return value instanceof Error && (value as { name?: string }).name === SESS_ERROR_NAME_DISPOSED;
return value instanceof SessDisposedError;
}
export function isSessAlreadyCreatedError(value: unknown): value is SessAlreadyCreatedError {
return value instanceof Error && (value as { name?: string }).name === SESS_ERROR_NAME_ALREADY_CREATED;
return value instanceof SessAlreadyCreatedError;
}
export function isSessInvalidSessionError(value: unknown): value is SessInvalidSessionError {
return value instanceof Error && (value as { name?: string }).name === SESS_ERROR_NAME_INVALID_SESSION;
return value instanceof SessInvalidSessionError;
}

@ -7,13 +7,20 @@ import {
SessDisposedError,
SessInvalidSessionError
} from '../errors.ts';
import {
SESS_ERR_ALREADY_CREATED,
SESS_ERR_DISPOSED,
SESS_ERR_INVALID_SESSION
} from '../consts.ts';
import { CodeError } from '$libs/errs';
describe('SessDisposedError', () => {
it('carries a stable name', () => {
it('carries the disposed code as the error name', () => {
const err = new SessDisposedError('boom');
expect(err.name).toBe('SessDisposedError');
expect(err.name).toBe(SESS_ERR_DISPOSED);
expect(err.code).toBe(SESS_ERR_DISPOSED);
expect(err.message).toBe('boom');
expect(err).toBeInstanceOf(Error);
expect(err).toBeInstanceOf(CodeError);
});
it('matches its type guard', () => {
@ -24,10 +31,11 @@ describe('SessDisposedError', () => {
});
describe('SessAlreadyCreatedError', () => {
it('carries a stable name', () => {
it('carries the already-created code as the error name', () => {
const err = new SessAlreadyCreatedError('boom');
expect(err.name).toBe('SessAlreadyCreatedError');
expect(err).toBeInstanceOf(Error);
expect(err.name).toBe(SESS_ERR_ALREADY_CREATED);
expect(err.code).toBe(SESS_ERR_ALREADY_CREATED);
expect(err).toBeInstanceOf(CodeError);
});
it('matches its type guard', () => {
@ -38,9 +46,10 @@ describe('SessAlreadyCreatedError', () => {
});
describe('SessInvalidSessionError', () => {
it('carries a stable name and the invariant', () => {
it('carries the invalid-session code and the invariant', () => {
const err = new SessInvalidSessionError('expiresAt_not_finite', 'msg');
expect(err.name).toBe('SessInvalidSessionError');
expect(err.name).toBe(SESS_ERR_INVALID_SESSION);
expect(err.code).toBe(SESS_ERR_INVALID_SESSION);
expect(err.invariant).toBe('expiresAt_not_finite');
expect(err.message).toBe('msg');
});

@ -3,51 +3,50 @@
* — task failures at runtime are reported as `TIMR_EVENT_FAILED`, not
* thrown.
*
* Each class carries a literal `name` so it survives serialisation
* across worker boundaries and is stable to test against.
* Each class extends `CodeError` (from `$libs/errs`) and carries an
* `ErrCode` that doubles as the error name for stable test matching
* and serialization.
*/
import { CodeError } from '$libs/errs';
import {
TIMR_ERROR_NAME_DISPOSED,
TIMR_ERROR_NAME_DUPLICATE_KEY,
TIMR_ERROR_NAME_INACTIVE_TIMER,
TIMR_ERROR_NAME_INVALID_DELAY,
TIMR_ERROR_NAME_INVALID_KEY
} from './consts.ts';
TIMR_ERR_DISPOSED,
TIMR_ERR_DUPLICATE_KEY,
TIMR_ERR_INACTIVE_TIMER,
TIMR_ERR_INVALID_DELAY,
TIMR_ERR_INVALID_KEY
} from '$libs/timers';
abstract class TimrEngineError extends Error {
abstract readonly name: string;
export class TimrDisposedError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(message, options);
super(TIMR_ERR_DISPOSED, { message, cause: options?.cause });
}
}
export class TimrDisposedError extends TimrEngineError {
readonly name = TIMR_ERROR_NAME_DISPOSED;
}
export class TimrInvalidKeyError extends TimrEngineError {
readonly name = TIMR_ERROR_NAME_INVALID_KEY;
export class TimrInvalidKeyError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(TIMR_ERR_INVALID_KEY, { message, cause: options?.cause });
}
}
export class TimrDuplicateKeyError extends TimrEngineError {
readonly name = TIMR_ERROR_NAME_DUPLICATE_KEY;
export class TimrDuplicateKeyError extends CodeError {
readonly key: string;
constructor(message: string, key: string) {
super(message);
super(TIMR_ERR_DUPLICATE_KEY, { message });
this.key = key;
}
}
export class TimrInvalidDelayError extends TimrEngineError {
readonly name = TIMR_ERROR_NAME_INVALID_DELAY;
export class TimrInvalidDelayError extends CodeError {
constructor(message: string, options?: { cause?: unknown }) {
super(TIMR_ERR_INVALID_DELAY, { message, cause: options?.cause });
}
}
export class TimrInactiveTimerError extends TimrEngineError {
readonly name = TIMR_ERROR_NAME_INACTIVE_TIMER;
export class TimrInactiveTimerError extends CodeError {
readonly key: string;
constructor(message: string, key: string) {
super(message);
super(TIMR_ERR_INACTIVE_TIMER, { message });
this.key = key;
}
}
@ -55,21 +54,21 @@ export class TimrInactiveTimerError extends TimrEngineError {
// ── Type guards ────────────────────────────────────────────────────────────
export function isTimrDisposedError(value: unknown): value is TimrDisposedError {
return value instanceof Error && (value as { name?: string }).name === TIMR_ERROR_NAME_DISPOSED;
return value instanceof TimrDisposedError;
}
export function isTimrInvalidKeyError(value: unknown): value is TimrInvalidKeyError {
return value instanceof Error && (value as { name?: string }).name === TIMR_ERROR_NAME_INVALID_KEY;
return value instanceof TimrInvalidKeyError;
}
export function isTimrDuplicateKeyError(value: unknown): value is TimrDuplicateKeyError {
return value instanceof Error && (value as { name?: string }).name === TIMR_ERROR_NAME_DUPLICATE_KEY;
return value instanceof TimrDuplicateKeyError;
}
export function isTimrInvalidDelayError(value: unknown): value is TimrInvalidDelayError {
return value instanceof Error && (value as { name?: string }).name === TIMR_ERROR_NAME_INVALID_DELAY;
return value instanceof TimrInvalidDelayError;
}
export function isTimrInactiveTimerError(value: unknown): value is TimrInactiveTimerError {
return value instanceof Error && (value as { name?: string }).name === TIMR_ERROR_NAME_INACTIVE_TIMER;
return value instanceof TimrInactiveTimerError;
}

@ -290,6 +290,50 @@ export function isAuthRouteNotFoundError(value: unknown): value is AuthRouteNotF
return value instanceof AuthRouteNotFoundError;
}
export function isAuthAccountNotLinkedError(value: unknown): value is AuthAccountNotLinkedError {
return value instanceof AuthAccountNotLinkedError;
}
export function isAuthSessionRevokedError(value: unknown): value is AuthSessionRevokedError {
return value instanceof AuthSessionRevokedError;
}
export function isAuthAssuranceRequiredError(
value: unknown
): value is AuthAssuranceRequiredError {
return value instanceof AuthAssuranceRequiredError;
}
export function isAuthRateLimitedError(value: unknown): value is AuthRateLimitedError {
return value instanceof AuthRateLimitedError;
}
export function isAuthTenantBoundaryError(value: unknown): value is AuthTenantBoundaryError {
return value instanceof AuthTenantBoundaryError;
}
export function isAuthOAuthStateInvalidError(
value: unknown
): value is AuthOAuthStateInvalidError {
return value instanceof AuthOAuthStateInvalidError;
}
export function isAuthOAuthProviderError(value: unknown): value is AuthOAuthProviderError {
return value instanceof AuthOAuthProviderError;
}
export function isAuthOtpInvalidError(value: unknown): value is AuthOtpInvalidError {
return value instanceof AuthOtpInvalidError;
}
export function isAuthMfaRequiredError(value: unknown): value is AuthMfaRequiredError {
return value instanceof AuthMfaRequiredError;
}
export function isAuthWebAuthnError(value: unknown): value is AuthWebAuthnError {
return value instanceof AuthWebAuthnError;
}
export function toAuthSafeError(value: unknown): {
readonly code: AuthErrorCode;
readonly messageKey: string;

@ -1,3 +1,5 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
export const BUSS_MODULE = 'buss';
export const BUSS_DEFAULT_SOURCE = 'buss';
export const BUSS_EVENT_ALL = '*';
@ -27,10 +29,13 @@ export const BUSS_DIAGNOSTIC_EVENTS = {
LISTENER_LEAK_WARNING: 'buss.listener.leak_warning'
} as const;
export const BUSS_ERROR_NAME_DISPOSED = 'BusDisposedError';
export const BUSS_ERROR_NAME_AGGREGATE_LISTENER = 'BusAggregateListenerError';
export const BUSS_ERROR_NAME_REENTRANCY_LIMIT = 'BusReentrancyLimitError';
export const BUSS_ERROR_NAME_INVALID_PAYLOAD = 'BusInvalidPayloadError';
// ── Error codes ────────────────────────────────────────────────────────
export const BUSS_ERR: ModuleSeed = moduleSeed(BUSS_MODULE);
export const BUSS_ERR_DISPOSED: ErrCode = errCode(BUSS_ERR, 'disposed');
export const BUSS_ERR_AGGREGATE_LISTENER: ErrCode = errCode(BUSS_ERR, 'aggregate_listener');
export const BUSS_ERR_REENTRANCY_LIMIT: ErrCode = errCode(BUSS_ERR, 'reentrancy_limit');
export const BUSS_ERR_INVALID_PAYLOAD: ErrCode = errCode(BUSS_ERR, 'invalid_payload');
export const BUSS_ERROR_MSG_DISPOSED = `[${BUSS_MODULE}] operation called on a disposed bus`;
export const BUSS_ERROR_MSG_AGGREGATE_LISTENER = `[${BUSS_MODULE}] one or more listeners failed`;

@ -1,55 +1,57 @@
import { CodeError } from '$libs/errs';
import {
BUSS_ERR_AGGREGATE_LISTENER,
BUSS_ERR_DISPOSED,
BUSS_ERR_INVALID_PAYLOAD,
BUSS_ERR_REENTRANCY_LIMIT,
BUSS_ERROR_MSG_AGGREGATE_LISTENER,
BUSS_ERROR_MSG_DISPOSED,
BUSS_ERROR_MSG_INVALID_PAYLOAD,
BUSS_ERROR_MSG_REENTRANCY_LIMIT,
BUSS_ERROR_NAME_AGGREGATE_LISTENER,
BUSS_ERROR_NAME_DISPOSED,
BUSS_ERROR_NAME_INVALID_PAYLOAD,
BUSS_ERROR_NAME_REENTRANCY_LIMIT
BUSS_ERROR_MSG_REENTRANCY_LIMIT
} from './consts.ts';
import type { BusEnvelope, BusListenerFailure } from './types.ts';
export class BusDisposedError extends Error {
export class BusDisposedError extends CodeError {
constructor(message = BUSS_ERROR_MSG_DISPOSED) {
super(message);
this.name = BUSS_ERROR_NAME_DISPOSED;
super(BUSS_ERR_DISPOSED, { message });
}
}
export class BusAggregateListenerError extends Error {
export class BusAggregateListenerError extends CodeError {
readonly envelope: BusEnvelope;
readonly failures: readonly BusListenerFailure[];
constructor(failures: readonly BusListenerFailure[], envelope: BusEnvelope) {
super(BUSS_ERROR_MSG_AGGREGATE_LISTENER);
this.name = BUSS_ERROR_NAME_AGGREGATE_LISTENER;
super(BUSS_ERR_AGGREGATE_LISTENER, { message: BUSS_ERROR_MSG_AGGREGATE_LISTENER });
this.failures = failures;
this.envelope = envelope;
}
}
export class BusReentrancyLimitError extends Error {
export class BusReentrancyLimitError extends CodeError {
readonly type: string;
readonly depth: number;
readonly maxDepth: number;
constructor(type: string, depth: number, maxDepth: number) {
super(`${BUSS_ERROR_MSG_REENTRANCY_LIMIT} (type=${type}, depth=${depth}, max=${maxDepth})`);
this.name = BUSS_ERROR_NAME_REENTRANCY_LIMIT;
super(BUSS_ERR_REENTRANCY_LIMIT, {
message: `${BUSS_ERROR_MSG_REENTRANCY_LIMIT} (type=${type}, depth=${depth}, max=${maxDepth})`
});
this.type = type;
this.depth = depth;
this.maxDepth = maxDepth;
}
}
export class BusInvalidPayloadError extends Error {
export class BusInvalidPayloadError extends CodeError {
readonly type: string;
readonly cause: unknown;
override readonly cause: unknown;
constructor(type: string, cause: unknown, message = BUSS_ERROR_MSG_INVALID_PAYLOAD) {
super(`${message} (type=${type})`);
this.name = BUSS_ERROR_NAME_INVALID_PAYLOAD;
super(BUSS_ERR_INVALID_PAYLOAD, {
message: `${message} (type=${type})`,
cause
});
this.type = type;
this.cause = cause;
}
@ -71,8 +73,6 @@ export function isBusReentrancyLimitError(
return error instanceof BusReentrancyLimitError;
}
export function isBusInvalidPayloadError(
error: unknown
): error is BusInvalidPayloadError {
export function isBusInvalidPayloadError(error: unknown): error is BusInvalidPayloadError {
return error instanceof BusInvalidPayloadError;
}

@ -18,14 +18,15 @@ export {
BUSS_DEFAULT_MAX_REENTRANCY_DEPTH,
BUSS_DEFAULT_SOURCE,
BUSS_DIAGNOSTIC_EVENTS,
BUSS_ERR,
BUSS_ERR_AGGREGATE_LISTENER,
BUSS_ERR_DISPOSED,
BUSS_ERR_INVALID_PAYLOAD,
BUSS_ERR_REENTRANCY_LIMIT,
BUSS_ERROR_MSG_AGGREGATE_LISTENER,
BUSS_ERROR_MSG_DISPOSED,
BUSS_ERROR_MSG_INVALID_PAYLOAD,
BUSS_ERROR_MSG_REENTRANCY_LIMIT,
BUSS_ERROR_NAME_AGGREGATE_LISTENER,
BUSS_ERROR_NAME_DISPOSED,
BUSS_ERROR_NAME_INVALID_PAYLOAD,
BUSS_ERROR_NAME_REENTRANCY_LIMIT,
BUSS_EVENT_ALL,
BUSS_ID_PREFIX,
BUSS_LISTENER_ERROR_MODE_COLLECT,

@ -1,3 +1,14 @@
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
// ── Error codes (shared by libs/cach, arts/cach, svrs/cach) ────────────
export const CACH_ERR: ModuleSeed = moduleSeed('cach');
export const CACH_ERR_KEY: ErrCode = errCode(CACH_ERR, 'key');
export const CACH_ERR_SCOPE: ErrCode = errCode(CACH_ERR, 'scope');
export const CACH_ERR_POLICY: ErrCode = errCode(CACH_ERR, 'policy');
export const CACH_ERR_DISPOSED: ErrCode = errCode(CACH_ERR, 'disposed');
export const CACH_ERR_ACTIVE_ENTRY_DISPOSED: ErrCode = errCode(CACH_ERR, 'active_entry_disposed');
export const CACH_DEFAULT_NAMESPACE = 'cach';
export const CACH_DEFAULT_VERSION = '1';
export const CACH_DEFAULT_SCHEMA_VERSION = 'default';
@ -196,10 +207,6 @@ export const CACH_DEFAULT_CACHE_POLICY = {
persist: true
} as const;
export const CACH_ERROR_NAME_KEY = 'CacheKeyError';
export const CACH_ERROR_NAME_SCOPE = 'CacheScopeError';
export const CACH_ERROR_NAME_POLICY = 'CachePolicyError';
export const CACH_ERROR_MESSAGES = {
KEY_MUST_BE_ARRAY: 'Invalid cache key: key must be an array.',
TAG_STRING_EMPTY: 'Invalid cache tag: tag string cannot be empty.',

@ -1,23 +1,21 @@
import { CACH_ERROR_NAME_KEY, CACH_ERROR_NAME_POLICY, CACH_ERROR_NAME_SCOPE } from './consts.ts';
import { CodeError } from '$libs/errs';
import { CACH_ERR_KEY, CACH_ERR_POLICY, CACH_ERR_SCOPE } from './consts.ts';
export class CacheKeyError extends Error {
export class CacheKeyError extends CodeError {
constructor(message: string) {
super(message);
this.name = CACH_ERROR_NAME_KEY;
super(CACH_ERR_KEY, { message });
}
}
export class CacheScopeError extends Error {
export class CacheScopeError extends CodeError {
constructor(message: string) {
super(message);
this.name = CACH_ERROR_NAME_SCOPE;
super(CACH_ERR_SCOPE, { message });
}
}
export class CachePolicyError extends Error {
export class CachePolicyError extends CodeError {
constructor(message: string) {
super(message);
this.name = CACH_ERROR_NAME_POLICY;
super(CACH_ERR_POLICY, { message });
}
}

@ -1,3 +1,12 @@
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_EFFECT_ALLOW = 'allow';
export const PERM_EFFECT_DENY = 'deny';
export const PERM_EFFECT_INDETERMINATE = 'indeterminate';

@ -1,37 +1,36 @@
import { CodeError } from '$libs/errs';
import {
PERM_EFFECT_DENY,
PERM_ERROR_MSG_DENIED_PREFIX,
PERM_ERROR_NAME_DENIED,
PERM_ERROR_NAME_RUNTIME,
PERM_ERROR_NAME_SCHEMA
PERM_ERR_DENIED,
PERM_ERR_RUNTIME,
PERM_ERR_SCHEMA,
PERM_ERROR_MSG_DENIED_PREFIX
} from './consts.ts';
import type { PermissionDecision } from './types.ts';
export class PermissionDeniedError extends Error {
export class PermissionDeniedError extends CodeError {
readonly decision: PermissionDecision;
constructor(decision: PermissionDecision) {
super(
decision.effect === PERM_EFFECT_DENY
? decision.reason
: `${PERM_ERROR_MSG_DENIED_PREFIX}${decision.effect}`
);
this.name = PERM_ERROR_NAME_DENIED;
super(PERM_ERR_DENIED, {
message:
decision.effect === PERM_EFFECT_DENY
? decision.reason
: `${PERM_ERROR_MSG_DENIED_PREFIX}${decision.effect}`
});
this.decision = decision;
}
}
export class PermissionSchemaError extends Error {
export class PermissionSchemaError extends CodeError {
constructor(message: string) {
super(message);
this.name = PERM_ERROR_NAME_SCHEMA;
super(PERM_ERR_SCHEMA, { message });
}
}
export class PermissionRuntimeError extends Error {
export class PermissionRuntimeError extends CodeError {
constructor(message: string) {
super(message);
this.name = PERM_ERROR_NAME_RUNTIME;
super(PERM_ERR_RUNTIME, { message });
}
}

@ -8,9 +8,20 @@
* `arts/timr/consts.ts`.
*/
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
/** Logger category emitted by the engine. */
export const TIMR_MODULE = 'timr';
// ── Error codes ────────────────────────────────────────────────────────
export const TIMR_ERR: ModuleSeed = moduleSeed(TIMR_MODULE);
export const TIMR_ERR_DISPOSED: ErrCode = errCode(TIMR_ERR, 'disposed');
export const TIMR_ERR_INVALID_KEY: ErrCode = errCode(TIMR_ERR, 'invalid_key');
export const TIMR_ERR_DUPLICATE_KEY: ErrCode = errCode(TIMR_ERR, 'duplicate_key');
export const TIMR_ERR_INVALID_DELAY: ErrCode = errCode(TIMR_ERR, 'invalid_delay');
export const TIMR_ERR_INACTIVE_TIMER: ErrCode = errCode(TIMR_ERR, 'inactive_timer');
export const TIMR_DIAGNOSTIC_EVENTS = {
LISTENER_THREW: 'timr.listener_threw',
SCHEDULE_AT_PAST: 'timr.schedule_at_past',

@ -53,7 +53,6 @@ export const CACH_LOG_MESSAGE_BY_EVENT: Record<CacheEventType, string> = {
[CACH_EVENT_DELETE]: CACH_LOG_MESSAGE_DELETE
};
export const CACH_ERROR_NAME_DISPOSED = 'CachDisposedError';
export const CACH_ERROR_MSG_DISPOSED_SUFFIX = '() called on a disposed cache engine';
// Method labels for `ensureLive(method)` error messages. Scoped with

@ -1,7 +1,10 @@
import { CACH_ERROR_NAME_DISPOSED } from './consts.ts';
import { CodeError } from '$libs/errs';
import { CACH_ERR_DISPOSED } from '$libs/cach';
export class CachDisposedError extends Error {
override readonly name = CACH_ERROR_NAME_DISPOSED;
export class CachDisposedError extends CodeError {
constructor(message: string) {
super(CACH_ERR_DISPOSED, { message });
}
}
export function isCachDisposedError(error: unknown): error is CachDisposedError {

@ -1,8 +1,8 @@
export { createEngineCache } from './engine-cache.ts';
export { createCacheDiagnostics, emitCacheDiagnostic } from './diagnostics.ts';
export { CACH_ERR_DISPOSED } from '$libs/cach';
export {
CACH_ERROR_MSG_DISPOSED_SUFFIX,
CACH_ERROR_NAME_DISPOSED,
CACH_LOG_MESSAGE_ADAPTER_ERROR,
CACH_LOG_MESSAGE_BY_EVENT,
CACH_LOG_MESSAGE_DELETE,

@ -1,3 +1,6 @@
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 = {
@ -13,8 +16,10 @@ 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';
export const PERM_ERROR_NAME_INVALID_BODY = 'PermInvalidBodyError';
export const PERM_ERROR_NAME_DISPOSED = 'PermDisposedError';
// ── 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';

@ -1,11 +1,16 @@
import { PERM_ERROR_NAME_DISPOSED, PERM_ERROR_NAME_INVALID_BODY } from './consts.ts';
import { CodeError } from '$libs/errs';
import { PERM_ERR_INVALID_BODY, PERM_ERR_SERVER_DISPOSED } from './consts.ts';
export class PermInvalidBodyError extends Error {
override readonly name = PERM_ERROR_NAME_INVALID_BODY;
export class PermInvalidBodyError extends CodeError {
constructor(message: string) {
super(PERM_ERR_INVALID_BODY, { message });
}
}
export class PermDisposedError extends Error {
override readonly name = PERM_ERROR_NAME_DISPOSED;
export class PermDisposedError extends CodeError {
constructor(message: string) {
super(PERM_ERR_SERVER_DISPOSED, { message });
}
}
export function isPermInvalidBodyError(error: unknown): error is PermInvalidBodyError {

@ -17,8 +17,8 @@ export {
PERM_DIAGNOSTIC_EVENTS,
PERM_ERROR_MSG_BODY_MUST_BE_OBJECT,
PERM_ERROR_MSG_DISPOSED_SUFFIX,
PERM_ERROR_NAME_DISPOSED,
PERM_ERROR_NAME_INVALID_BODY,
PERM_ERR_SERVER_DISPOSED,
PERM_ERR_INVALID_BODY,
PERM_LOG_MSG_DECISION,
PERM_LOG_MSG_DENIED,
PERM_LOG_MSG_INDETERMINATE,

Loading…
Cancel
Save

Powered by TurnKey Linux.