Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
parent
dace6d00d1
commit
4db6bd2b3b
@ -1,29 +0,0 @@
|
||||
export { AAPP_MODULE } from '$libs/aapp/consts';
|
||||
import { AAPP_ERR, AAPP_MODULE } from '$libs/aapp/consts';
|
||||
import { errCode, type ErrCode } from '$libs/errs';
|
||||
|
||||
export const AAPP_LOG_MSG_SESSION_CACHE_CLEAR_FAILED =
|
||||
'session change cache clear failed';
|
||||
export const AAPP_CONNECTION_CLOSE_REASON_IDENTITY_CLEARED = 'identity_cleared';
|
||||
|
||||
export const AAPP_ORCHESTRATION_STANDARD = 'standard';
|
||||
export const AAPP_ORCHESTRATION_SILENT = 'silent';
|
||||
export const AAPP_ORCHESTRATION_TRANSLATOR_IDENTITY = 'identity';
|
||||
export const AAPP_ORCHESTRATION_TRANSLATOR_PERMISSIONS_REFRESH = 'permissions-refresh';
|
||||
export const AAPP_ORCHESTRATION_TRANSLATOR_TENANT_SWITCHED = 'tenant-switched';
|
||||
export const AAPP_ORCHESTRATION_TRANSLATOR_CONNECTIVITY = 'connectivity';
|
||||
export const AAPP_ORCHESTRATION_TRANSLATOR_DISPOSE = 'dispose';
|
||||
|
||||
export const AAPP_ERR_ALREADY_CREATED: ErrCode = errCode(AAPP_ERR, 'already_created');
|
||||
|
||||
export const AAPP_ERROR_ALREADY_CREATED_SESSION =
|
||||
`[${AAPP_MODULE}] App.createActiveSession() called twice — only one session per App.`;
|
||||
|
||||
export const AAPP_ERROR_ALREADY_CREATED_PERMISSIONS =
|
||||
`[${AAPP_MODULE}] App.createActivePermissions() called twice — only one permissions client per App.`;
|
||||
|
||||
export const AAPP_ERROR_ALREADY_CREATED_AUTH =
|
||||
`[${AAPP_MODULE}] App.createActiveAuth() called twice — only one active auth client per App.`;
|
||||
|
||||
export const AAPP_ERROR_CREATE_PERMISSIONS_ENDPOINT_REQUIRED =
|
||||
`[${AAPP_MODULE}] App.createActivePermissions() requires endpoint via options.permissions or argument.`;
|
||||
@ -1,64 +0,0 @@
|
||||
import type { AppEventMap, AppUserIdentityChangeCause } from '$libs/aapp/events';
|
||||
import {
|
||||
AAPP_USER_IDENTITY_CAUSE_SESSION_ADOPTED,
|
||||
AAPP_USER_IDENTITY_CAUSE_SESSION_ADOPTED_SERVER,
|
||||
AAPP_USER_IDENTITY_CAUSE_SESSION_EXPIRED,
|
||||
AAPP_USER_IDENTITY_CAUSE_SESSION_EXTERNAL_CHANGED,
|
||||
AAPP_USER_IDENTITY_CAUSE_SESSION_REFRESHED,
|
||||
AAPP_USER_IDENTITY_CAUSE_SESSION_REVOKED,
|
||||
publishAppUserIdentityChanged
|
||||
} from '$libs/aapp/events';
|
||||
import {
|
||||
SESS_EVENT_LIFECYCLE_ADOPTED,
|
||||
SESS_EVENT_LIFECYCLE_ADOPTED_SERVER,
|
||||
SESS_EVENT_LIFECYCLE_EXPIRED,
|
||||
SESS_EVENT_LIFECYCLE_EXTERNAL_CHANGED,
|
||||
SESS_EVENT_LIFECYCLE_REFRESHED,
|
||||
SESS_EVENT_LIFECYCLE_REVOKED,
|
||||
SESS_EVENT_CHANGED
|
||||
} from '$sess/consts';
|
||||
import type { SessEventMap, SessLifecyclePayload, SessionEvent } from '$sess/types';
|
||||
import type { EngineBus } from '$libs/buss';
|
||||
import { AAPP_MODULE } from '../consts.ts';
|
||||
|
||||
type SessionTranslatorBusEvents = AppEventMap & SessEventMap;
|
||||
|
||||
export function wireSessionTranslator(bus: EngineBus<SessionTranslatorBusEvents>): () => void {
|
||||
const subscription = bus.on(SESS_EVENT_CHANGED, (event) => {
|
||||
publishAppIdentityFromSessionLifecycleEvent(bus, event.payload);
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}
|
||||
|
||||
export function publishAppIdentityFromSessionLifecycleEvent(
|
||||
bus: EngineBus<SessionTranslatorBusEvents>,
|
||||
payload: SessLifecyclePayload
|
||||
): void {
|
||||
const cause = resolveAppIdentityCause(payload.event);
|
||||
if (cause === undefined) return;
|
||||
publishAppUserIdentityChanged(
|
||||
bus,
|
||||
{
|
||||
event: payload.event,
|
||||
generation: payload.generation,
|
||||
identity: {
|
||||
from: payload.identity.from,
|
||||
to: payload.identity.to
|
||||
},
|
||||
cause
|
||||
},
|
||||
{
|
||||
source: AAPP_MODULE
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function resolveAppIdentityCause(event: SessionEvent): AppUserIdentityChangeCause | undefined {
|
||||
if (event === SESS_EVENT_LIFECYCLE_ADOPTED) return AAPP_USER_IDENTITY_CAUSE_SESSION_ADOPTED;
|
||||
if (event === SESS_EVENT_LIFECYCLE_ADOPTED_SERVER) return AAPP_USER_IDENTITY_CAUSE_SESSION_ADOPTED_SERVER;
|
||||
if (event === SESS_EVENT_LIFECYCLE_REFRESHED) return AAPP_USER_IDENTITY_CAUSE_SESSION_REFRESHED;
|
||||
if (event === SESS_EVENT_LIFECYCLE_REVOKED) return AAPP_USER_IDENTITY_CAUSE_SESSION_REVOKED;
|
||||
if (event === SESS_EVENT_LIFECYCLE_EXPIRED) return AAPP_USER_IDENTITY_CAUSE_SESSION_EXPIRED;
|
||||
if (event === SESS_EVENT_LIFECYCLE_EXTERNAL_CHANGED) return AAPP_USER_IDENTITY_CAUSE_SESSION_EXTERNAL_CHANGED;
|
||||
return undefined;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
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';
|
||||
|
||||
export const APP_LOG_MSG_SESSION_CACHE_CLEAR_FAILED =
|
||||
'session change cache clear failed';
|
||||
export const APP_CONNECTION_CLOSE_REASON_IDENTITY_CLEARED = 'identity_cleared';
|
||||
|
||||
export const APP_ORCHESTRATION_STANDARD = 'standard';
|
||||
export const APP_ORCHESTRATION_SILENT = 'silent';
|
||||
export const APP_ORCHESTRATION_TRANSLATOR_IDENTITY = 'identity';
|
||||
export const APP_ORCHESTRATION_TRANSLATOR_PERMISSION_REFRESH = 'permissions-refresh';
|
||||
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.`;
|
||||
|
||||
export const APP_ERROR_ALREADY_CREATED_PERMISSIONS =
|
||||
`[${APP_MODULE}] App.createActivePermissions() called twice — only one permissions client per App.`;
|
||||
|
||||
export const APP_ERROR_ALREADY_CREATED_AUTH =
|
||||
`[${APP_MODULE}] App.createActiveAuth() called twice — only one active auth client per App.`;
|
||||
|
||||
export const APP_ERROR_CREATE_PERMISSION_ENDPOINT_REQUIRED =
|
||||
`[${APP_MODULE}] App.createActivePermissions() requires endpoint via options.permissions or argument.`;
|
||||
@ -1,9 +1,9 @@
|
||||
import { CodeError } from '$libs/errs';
|
||||
import { AAPP_ERR_ALREADY_CREATED } from './consts.ts';
|
||||
import { APP_ERR_ALREADY_CREATED } from './consts.ts';
|
||||
|
||||
export class AappAlreadyCreatedError extends CodeError {
|
||||
constructor(message: string) {
|
||||
super(AAPP_ERR_ALREADY_CREATED, { message });
|
||||
super(APP_ERR_ALREADY_CREATED, { message });
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import type { AppEventMap, AppUserIdentityChangeCause } from '$libs/active-app/events';
|
||||
import {
|
||||
APP_USER_IDENTITY_CAUSE_SESSION_ADOPTED,
|
||||
APP_USER_IDENTITY_CAUSE_SESSION_ADOPTED_SERVER,
|
||||
APP_USER_IDENTITY_CAUSE_SESSION_EXPIRED,
|
||||
APP_USER_IDENTITY_CAUSE_SESSION_EXTERNAL_CHANGED,
|
||||
APP_USER_IDENTITY_CAUSE_SESSION_REFRESHED,
|
||||
APP_USER_IDENTITY_CAUSE_SESSION_REVOKED,
|
||||
publishAppUserIdentityChanged
|
||||
} from '$libs/active-app/events';
|
||||
import {
|
||||
SESSION_EVENT_LIFECYCLE_ADOPTED,
|
||||
SESSION_EVENT_LIFECYCLE_ADOPTED_SERVER,
|
||||
SESSION_EVENT_LIFECYCLE_EXPIRED,
|
||||
SESSION_EVENT_LIFECYCLE_EXTERNAL_CHANGED,
|
||||
SESSION_EVENT_LIFECYCLE_REFRESHED,
|
||||
SESSION_EVENT_LIFECYCLE_REVOKED,
|
||||
SESSION_EVENT_CHANGED
|
||||
} from '$session/consts';
|
||||
import type { SessEventMap, SessLifecyclePayload, SessionEvent } from '$session/types';
|
||||
import type { EngineBus } from '$libs/bus';
|
||||
import { APP_MODULE } from '../consts.ts';
|
||||
|
||||
type SessionTranslatorBusEvents = AppEventMap & SessEventMap;
|
||||
|
||||
export function wireSessionTranslator(bus: EngineBus<SessionTranslatorBusEvents>): () => void {
|
||||
const subscription = bus.on(SESSION_EVENT_CHANGED, (event) => {
|
||||
publishAppIdentityFromSessionLifecycleEvent(bus, event.payload);
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}
|
||||
|
||||
export function publishAppIdentityFromSessionLifecycleEvent(
|
||||
bus: EngineBus<SessionTranslatorBusEvents>,
|
||||
payload: SessLifecyclePayload
|
||||
): void {
|
||||
const cause = resolveAppIdentityCause(payload.event);
|
||||
if (cause === undefined) return;
|
||||
publishAppUserIdentityChanged(
|
||||
bus,
|
||||
{
|
||||
event: payload.event,
|
||||
generation: payload.generation,
|
||||
identity: {
|
||||
from: payload.identity.from,
|
||||
to: payload.identity.to
|
||||
},
|
||||
cause
|
||||
},
|
||||
{
|
||||
source: APP_MODULE
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function resolveAppIdentityCause(event: SessionEvent): AppUserIdentityChangeCause | undefined {
|
||||
if (event === SESSION_EVENT_LIFECYCLE_ADOPTED) return APP_USER_IDENTITY_CAUSE_SESSION_ADOPTED;
|
||||
if (event === SESSION_EVENT_LIFECYCLE_ADOPTED_SERVER) return APP_USER_IDENTITY_CAUSE_SESSION_ADOPTED_SERVER;
|
||||
if (event === SESSION_EVENT_LIFECYCLE_REFRESHED) return APP_USER_IDENTITY_CAUSE_SESSION_REFRESHED;
|
||||
if (event === SESSION_EVENT_LIFECYCLE_REVOKED) return APP_USER_IDENTITY_CAUSE_SESSION_REVOKED;
|
||||
if (event === SESSION_EVENT_LIFECYCLE_EXPIRED) return APP_USER_IDENTITY_CAUSE_SESSION_EXPIRED;
|
||||
if (event === SESSION_EVENT_LIFECYCLE_EXTERNAL_CHANGED) return APP_USER_IDENTITY_CAUSE_SESSION_EXTERNAL_CHANGED;
|
||||
return undefined;
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import { CACH_MODULE } from '$svrs/cach';
|
||||
|
||||
export {
|
||||
CACH_ERROR_MSG_DISPOSED_SUFFIX,
|
||||
CACH_LOG_MESSAGE_ADAPTER_ERROR,
|
||||
CACH_LOG_MESSAGE_BY_EVENT,
|
||||
CACH_LOG_MESSAGE_DELETE,
|
||||
CACH_LOG_MESSAGE_EVICTION,
|
||||
CACH_LOG_MESSAGE_HIT,
|
||||
CACH_LOG_MESSAGE_INVALIDATE,
|
||||
CACH_LOG_MESSAGE_MISS,
|
||||
CACH_LOG_MESSAGE_REFRESH_ERROR,
|
||||
CACH_LOG_MESSAGE_REFRESH_START,
|
||||
CACH_LOG_MESSAGE_REFRESH_SUCCESS,
|
||||
CACH_LOG_MESSAGE_SCHEMA_MISMATCH,
|
||||
CACH_LOG_MESSAGE_SCOPE_ERROR,
|
||||
CACH_LOG_MESSAGE_SET,
|
||||
CACH_LOG_MESSAGE_SINGLEFLIGHT_JOIN,
|
||||
CACH_LOG_MESSAGE_STALE_HIT,
|
||||
CACH_LOG_MESSAGE_STALE_IF_ERROR,
|
||||
CACH_METHOD_CLEAR,
|
||||
CACH_METHOD_EXPLAIN,
|
||||
CACH_METHOD_GET,
|
||||
CACH_METHOD_INVALIDATE,
|
||||
CACH_METHOD_MUTATE,
|
||||
CACH_METHOD_ON,
|
||||
CACH_METHOD_QUERY,
|
||||
CACH_METHOD_SET,
|
||||
CACH_METHOD_STATS,
|
||||
CACH_MODULE
|
||||
} from '$svrs/cach';
|
||||
|
||||
export const CACH_ACTIVE_STATUS_IDLE = 'idle';
|
||||
export const CACH_ACTIVE_STATUS_LOADING = 'loading';
|
||||
export const CACH_ACTIVE_STATUS_SUCCESS = 'success';
|
||||
export const CACH_ACTIVE_STATUS_STALE = 'stale';
|
||||
export const CACH_ACTIVE_STATUS_REFRESHING = 'refreshing';
|
||||
export const CACH_ACTIVE_STATUS_ERROR = 'error';
|
||||
export const CACH_ACTIVE_STATUS_DEGRADED = 'degraded';
|
||||
|
||||
export const CACH_ACTIVE_STATUSES = [
|
||||
CACH_ACTIVE_STATUS_IDLE,
|
||||
CACH_ACTIVE_STATUS_LOADING,
|
||||
CACH_ACTIVE_STATUS_SUCCESS,
|
||||
CACH_ACTIVE_STATUS_STALE,
|
||||
CACH_ACTIVE_STATUS_REFRESHING,
|
||||
CACH_ACTIVE_STATUS_ERROR,
|
||||
CACH_ACTIVE_STATUS_DEGRADED
|
||||
] as const;
|
||||
|
||||
// Active cache entry event names. Scoped with `cach.entry.` so they do
|
||||
// not collide with the engine-level `cach.*` events nor with any other
|
||||
// artifact's events.
|
||||
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_ACTIVE_DIAGNOSTIC_EVENTS = {
|
||||
OPERATION_FAILED: 'cach.active.operation_failed'
|
||||
} as const;
|
||||
|
||||
export const CACH_ACTIVE_LOG_MESSAGE_OPERATION_FAILED = 'active cache operation failed';
|
||||
|
||||
export const CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED =
|
||||
`[${CACH_MODULE}] ActiveCacheEntry used after dispose().`;
|
||||
|
||||
export const CACH_AUTO_INVALIDATE_NONE = 'none';
|
||||
export const CACH_AUTO_INVALIDATE_STANDARD = 'standard';
|
||||
export const CACH_AUTO_INVALIDATE_USER_IDENTITY_CHANGE = 'userIdentityChange';
|
||||
export const CACH_AUTO_INVALIDATE_TENANT_SWITCHED = 'tenantSwitched';
|
||||
|
||||
export const CACH_METHOD_ENTRY = 'cach.entry';
|
||||
|
||||
export const CACH_ACTIVE_ENTRY_EVENTS = [
|
||||
CACH_ACTIVE_ENTRY_EVENT_LOAD,
|
||||
CACH_ACTIVE_ENTRY_EVENT_REFRESH,
|
||||
CACH_ACTIVE_ENTRY_EVENT_INVALIDATE,
|
||||
CACH_ACTIVE_ENTRY_EVENT_SET
|
||||
] as const;
|
||||
@ -1,17 +0,0 @@
|
||||
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 CodeError {
|
||||
constructor(message = CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED) {
|
||||
super(CACH_ERR_ACTIVE_ENTRY_DISPOSED, { message });
|
||||
}
|
||||
}
|
||||
|
||||
export function isCachActiveEntryDisposedError(
|
||||
error: unknown
|
||||
): error is CachActiveEntryDisposedError {
|
||||
return error instanceof CachActiveEntryDisposedError;
|
||||
}
|
||||
@ -1,196 +0,0 @@
|
||||
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,
|
||||
CACH_ACTIVE_ENTRY_EVENT_REFRESH,
|
||||
CACH_ACTIVE_ENTRY_EVENT_SET,
|
||||
CACH_ACTIVE_ENTRY_EVENTS,
|
||||
CACH_ACTIVE_DIAGNOSTIC_EVENTS,
|
||||
CACH_ACTIVE_LOG_MESSAGE_OPERATION_FAILED,
|
||||
CACH_ACTIVE_STATUS_DEGRADED,
|
||||
CACH_ACTIVE_STATUS_ERROR,
|
||||
CACH_ACTIVE_STATUS_IDLE,
|
||||
CACH_ACTIVE_STATUS_LOADING,
|
||||
CACH_ACTIVE_STATUS_REFRESHING,
|
||||
CACH_ACTIVE_STATUS_STALE,
|
||||
CACH_ACTIVE_STATUS_SUCCESS,
|
||||
CACH_ACTIVE_STATUSES,
|
||||
CACH_AUTO_INVALIDATE_NONE,
|
||||
CACH_AUTO_INVALIDATE_STANDARD,
|
||||
CACH_AUTO_INVALIDATE_TENANT_SWITCHED,
|
||||
CACH_AUTO_INVALIDATE_USER_IDENTITY_CHANGE,
|
||||
CACH_ERROR_MSG_ACTIVE_ENTRY_DISPOSED,
|
||||
CACH_ERROR_MSG_DISPOSED_SUFFIX,
|
||||
CACH_LOG_MESSAGE_ADAPTER_ERROR,
|
||||
CACH_LOG_MESSAGE_BY_EVENT,
|
||||
CACH_LOG_MESSAGE_DELETE,
|
||||
CACH_LOG_MESSAGE_EVICTION,
|
||||
CACH_LOG_MESSAGE_HIT,
|
||||
CACH_LOG_MESSAGE_INVALIDATE,
|
||||
CACH_LOG_MESSAGE_MISS,
|
||||
CACH_LOG_MESSAGE_REFRESH_ERROR,
|
||||
CACH_LOG_MESSAGE_REFRESH_START,
|
||||
CACH_LOG_MESSAGE_REFRESH_SUCCESS,
|
||||
CACH_LOG_MESSAGE_SCHEMA_MISMATCH,
|
||||
CACH_LOG_MESSAGE_SCOPE_ERROR,
|
||||
CACH_LOG_MESSAGE_SET,
|
||||
CACH_LOG_MESSAGE_SINGLEFLIGHT_JOIN,
|
||||
CACH_LOG_MESSAGE_STALE_HIT,
|
||||
CACH_LOG_MESSAGE_STALE_IF_ERROR,
|
||||
CACH_METHOD_CLEAR,
|
||||
CACH_METHOD_ENTRY,
|
||||
CACH_METHOD_EXPLAIN,
|
||||
CACH_METHOD_GET,
|
||||
CACH_METHOD_INVALIDATE,
|
||||
CACH_METHOD_MUTATE,
|
||||
CACH_METHOD_ON,
|
||||
CACH_METHOD_QUERY,
|
||||
CACH_METHOD_SET,
|
||||
CACH_METHOD_STATS,
|
||||
CACH_MODULE
|
||||
} from './consts.ts';
|
||||
export { createActiveCacheDiagnostics, emitActiveCacheDiagnostic } from './diagnostics.ts';
|
||||
export {
|
||||
CachActiveEntryDisposedError,
|
||||
CachDisposedError,
|
||||
isCachActiveEntryDisposedError,
|
||||
isCachDisposedError
|
||||
} from './errors.ts';
|
||||
export { disposedCacheMessage } from '$svrs/cach';
|
||||
export type {
|
||||
ActiveCacheDiagnosticEvent,
|
||||
ActiveCacheDiagnosticMeta,
|
||||
ActiveCacheDiagnostics,
|
||||
ActiveCacheDiagnosticType
|
||||
} from './diagnostics.ts';
|
||||
export type {
|
||||
ActiveCache,
|
||||
ActiveCacheEntry,
|
||||
ActiveCacheEntryEvent,
|
||||
ActiveCacheEntryListener,
|
||||
ActiveCacheEntryOptions,
|
||||
ActiveCacheEntrySnapshot,
|
||||
ActiveCacheEntryStatus,
|
||||
ActiveCacheAutoInvalidateOn,
|
||||
ActiveCacheAutoInvalidateTarget,
|
||||
ActiveCacheOptions,
|
||||
EngineCache,
|
||||
EngineCacheOptions
|
||||
} from './types.ts';
|
||||
|
||||
export {
|
||||
CACH_ADAPTER_MEMORY,
|
||||
CACH_ADAPTER_STORAGE,
|
||||
CACH_DECISION_ACTION_DELETE_AND_FETCH,
|
||||
CACH_DECISION_ACTION_FETCH,
|
||||
CACH_DECISION_ACTION_SERVE,
|
||||
CACH_DECISION_ACTION_SERVE_AND_REFRESH,
|
||||
CACH_DECISION_ACTION_SKIP_CACHE,
|
||||
CACH_DECISION_REASON_BYPASS_CACHE,
|
||||
CACH_DECISION_REASON_CACHE_MISS,
|
||||
CACH_DECISION_REASON_EXPIRED,
|
||||
CACH_DECISION_REASON_FRESH,
|
||||
CACH_DECISION_REASON_NO_STORE,
|
||||
CACH_DECISION_REASON_PREFIX_EPOCH_CHANGED,
|
||||
CACH_DECISION_REASON_SCHEMA_VERSION_MISMATCH,
|
||||
CACH_DECISION_REASON_SCOPE_MISMATCH,
|
||||
CACH_DECISION_REASON_STALE_WINDOW_VALID,
|
||||
CACH_DECISION_REASON_TAG_EPOCH_CHANGED,
|
||||
CACH_ENVELOPE_STATE_DEGRADED,
|
||||
CACH_ENVELOPE_STATE_EXPIRED,
|
||||
CACH_ENVELOPE_STATE_FRESH,
|
||||
CACH_ENVELOPE_STATE_INVALIDATED,
|
||||
CACH_ENVELOPE_STATE_STALE,
|
||||
CACH_EVENT_ADAPTER_ERROR,
|
||||
CACH_EVENT_ALL,
|
||||
CACH_EVENT_DELETE,
|
||||
CACH_EVENT_HIT,
|
||||
CACH_EVENT_INVALIDATE,
|
||||
CACH_EVENT_MISS,
|
||||
CACH_EVENT_REFRESH_ERROR,
|
||||
CACH_EVENT_REFRESH_START,
|
||||
CACH_EVENT_REFRESH_SUCCESS,
|
||||
CACH_EVENT_SCHEMA_MISMATCH,
|
||||
CACH_EVENT_SCOPE_ERROR,
|
||||
CACH_EVENT_SET,
|
||||
CACH_EVENT_SINGLEFLIGHT_JOIN,
|
||||
CACH_EVENT_STALE_HIT,
|
||||
CACH_EVENT_STALE_IF_ERROR,
|
||||
CACH_EVENT_EVICTION,
|
||||
CACH_POLICY_CATALOG,
|
||||
CACH_POLICY_IMMUTABLE,
|
||||
CACH_POLICY_INTERACTIVE,
|
||||
CACH_POLICY_PRIVATE_SESSION,
|
||||
CACH_POLICY_REALTIME,
|
||||
CACH_READ_MODE_BYPASS_CACHE,
|
||||
CACH_READ_MODE_CACHE_FIRST,
|
||||
CACH_READ_MODE_MUST_REVALIDATE,
|
||||
CACH_READ_MODE_NO_STORE,
|
||||
CACH_READ_MODE_STALE_WHILE_REVALIDATE,
|
||||
CACH_SCOPE_ACTOR,
|
||||
CACH_SCOPE_CUSTOM,
|
||||
CACH_SCOPE_PERMISSION,
|
||||
CACH_SCOPE_PUBLIC,
|
||||
CACH_SCOPE_TENANT,
|
||||
CacheKeyError,
|
||||
CachePolicyError,
|
||||
CacheScopeError,
|
||||
createMapStorage,
|
||||
defaultCachePolicies,
|
||||
durationToMs,
|
||||
isCacheKeyError,
|
||||
isCachePolicyError,
|
||||
isCacheScopeError,
|
||||
memoryCacheAdapter,
|
||||
normalizeKey,
|
||||
normalizeKeyPrefixes,
|
||||
normalizeTag,
|
||||
normalizeTags,
|
||||
resolveScope,
|
||||
stableHash,
|
||||
stableStringify,
|
||||
storageCacheAdapter
|
||||
} from '$svrs/cach';
|
||||
|
||||
export type {
|
||||
CacheAdapter,
|
||||
CacheAdapterSetOptions,
|
||||
CacheClock,
|
||||
CacheDecision,
|
||||
CacheDecisionAction,
|
||||
CacheDecisionReason,
|
||||
CacheEnvelope,
|
||||
CacheEnvelopeState,
|
||||
CacheEvent,
|
||||
CacheEventHandler,
|
||||
CacheEventSelector,
|
||||
CacheEventType,
|
||||
CacheExplain,
|
||||
CacheKey,
|
||||
CachePolicy,
|
||||
CacheReadMode,
|
||||
CacheRuntime,
|
||||
CacheRuntimeConfig,
|
||||
CacheScopeInput,
|
||||
CacheScopeMode,
|
||||
CacheStats,
|
||||
CacheTagLike,
|
||||
CacheTagObject,
|
||||
CustomCacheScope,
|
||||
ExplainOptions,
|
||||
GetOptions,
|
||||
InvalidateOptions,
|
||||
MemoryCacheAdapter,
|
||||
MemoryCacheAdapterOptions,
|
||||
MemoryCacheEvictReason,
|
||||
MutateOptions,
|
||||
QueryOptions,
|
||||
ResolvedCachePolicy,
|
||||
ResolvedCacheScope,
|
||||
ResolvedScopeValues,
|
||||
ScopeResolver,
|
||||
SetOptions,
|
||||
StorageCacheAdapterOptions,
|
||||
StorageLike
|
||||
} from '$svrs/cach';
|
||||
@ -0,0 +1,80 @@
|
||||
import { CACHE_MODULE } from '$svrs/cache';
|
||||
|
||||
export {
|
||||
CACHE_ERROR_MSG_DISPOSED_SUFFIX,
|
||||
CACHE_LOG_MESSAGE_ADAPTER_ERROR,
|
||||
CACHE_LOG_MESSAGE_BY_EVENT,
|
||||
CACHE_LOG_MESSAGE_DELETE,
|
||||
CACHE_LOG_MESSAGE_EVICTION,
|
||||
CACHE_LOG_MESSAGE_HIT,
|
||||
CACHE_LOG_MESSAGE_INVALIDATE,
|
||||
CACHE_LOG_MESSAGE_MISS,
|
||||
CACHE_LOG_MESSAGE_REFRESH_ERROR,
|
||||
CACHE_LOG_MESSAGE_REFRESH_START,
|
||||
CACHE_LOG_MESSAGE_REFRESH_SUCCESS,
|
||||
CACHE_LOG_MESSAGE_SCHEMA_MISMATCH,
|
||||
CACHE_LOG_MESSAGE_SCOPE_ERROR,
|
||||
CACHE_LOG_MESSAGE_SET,
|
||||
CACHE_LOG_MESSAGE_SINGLEFLIGHT_JOIN,
|
||||
CACHE_LOG_MESSAGE_STALE_HIT,
|
||||
CACHE_LOG_MESSAGE_STALE_IF_ERROR,
|
||||
CACHE_METHOD_CLEAR,
|
||||
CACHE_METHOD_EXPLAIN,
|
||||
CACHE_METHOD_GET,
|
||||
CACHE_METHOD_INVALIDATE,
|
||||
CACHE_METHOD_MUTATE,
|
||||
CACHE_METHOD_ON,
|
||||
CACHE_METHOD_QUERY,
|
||||
CACHE_METHOD_SET,
|
||||
CACHE_METHOD_STATS,
|
||||
CACHE_MODULE
|
||||
} from '$svrs/cache';
|
||||
|
||||
export const CACHE_ACTIVE_STATUS_IDLE = 'idle';
|
||||
export const CACHE_ACTIVE_STATUS_LOADING = 'loading';
|
||||
export const CACHE_ACTIVE_STATUS_SUCCESS = 'success';
|
||||
export const CACHE_ACTIVE_STATUS_STALE = 'stale';
|
||||
export const CACHE_ACTIVE_STATUS_REFRESHING = 'refreshing';
|
||||
export const CACHE_ACTIVE_STATUS_ERROR = 'error';
|
||||
export const CACHE_ACTIVE_STATUS_DEGRADED = 'degraded';
|
||||
|
||||
export const CACHE_ACTIVE_STATUSES = [
|
||||
CACHE_ACTIVE_STATUS_IDLE,
|
||||
CACHE_ACTIVE_STATUS_LOADING,
|
||||
CACHE_ACTIVE_STATUS_SUCCESS,
|
||||
CACHE_ACTIVE_STATUS_STALE,
|
||||
CACHE_ACTIVE_STATUS_REFRESHING,
|
||||
CACHE_ACTIVE_STATUS_ERROR,
|
||||
CACHE_ACTIVE_STATUS_DEGRADED
|
||||
] as const;
|
||||
|
||||
// Active cache entry event names. Scoped with `cache.entry.` so they do
|
||||
// not collide with the engine-level `cach.*` events nor with any other
|
||||
// artifact's events.
|
||||
export const CACHE_ACTIVE_ENTRY_EVENT_LOAD = 'cache.entry.load';
|
||||
export const CACHE_ACTIVE_ENTRY_EVENT_REFRESH = 'cache.entry.refresh';
|
||||
export const CACHE_ACTIVE_ENTRY_EVENT_INVALIDATE = 'cache.entry.invalidate';
|
||||
export const CACHE_ACTIVE_ENTRY_EVENT_SET = 'cache.entry.set';
|
||||
|
||||
export const CACHE_ACTIVE_DIAGNOSTIC_EVENTS = {
|
||||
OPERATION_FAILED: 'cache.active.operation_failed'
|
||||
} as const;
|
||||
|
||||
export const CACHE_ACTIVE_LOG_MESSAGE_OPERATION_FAILED = 'active cache operation failed';
|
||||
|
||||
export const CACHE_ERROR_MSG_ACTIVE_ENTRY_DISPOSED =
|
||||
`[${CACHE_MODULE}] ActiveCacheEntry used after dispose().`;
|
||||
|
||||
export const CACHE_AUTO_INVALIDATE_NONE = 'none';
|
||||
export const CACHE_AUTO_INVALIDATE_STANDARD = 'standard';
|
||||
export const CACHE_AUTO_INVALIDATE_USER_IDENTITY_CHANGE = 'userIdentityChange';
|
||||
export const CACHE_AUTO_INVALIDATE_TENANT_SWITCHED = 'tenantSwitched';
|
||||
|
||||
export const CACHE_METHOD_ENTRY = 'cache.entry';
|
||||
|
||||
export const CACHE_ACTIVE_ENTRY_EVENTS = [
|
||||
CACHE_ACTIVE_ENTRY_EVENT_LOAD,
|
||||
CACHE_ACTIVE_ENTRY_EVENT_REFRESH,
|
||||
CACHE_ACTIVE_ENTRY_EVENT_INVALIDATE,
|
||||
CACHE_ACTIVE_ENTRY_EVENT_SET
|
||||
] as const;
|
||||
@ -0,0 +1,17 @@
|
||||
import { CodeError } from '$libs/errs';
|
||||
import { CACHE_ERR_ACTIVE_ENTRY_DISPOSED } from '$libs/cache';
|
||||
import { CACHE_ERROR_MSG_ACTIVE_ENTRY_DISPOSED } from './consts.ts';
|
||||
|
||||
export { CacheDisposedError, isCacheDisposedError } from '$svrs/cache';
|
||||
|
||||
export class CacheActiveEntryDisposedError extends CodeError {
|
||||
constructor(message = CACHE_ERROR_MSG_ACTIVE_ENTRY_DISPOSED) {
|
||||
super(CACHE_ERR_ACTIVE_ENTRY_DISPOSED, { message });
|
||||
}
|
||||
}
|
||||
|
||||
export function isCacheActiveEntryDisposedError(
|
||||
error: unknown
|
||||
): error is CacheActiveEntryDisposedError {
|
||||
return error instanceof CacheActiveEntryDisposedError;
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
export { createActiveCache } from './active-cache.svelte.ts';
|
||||
export { CACHE_ERR, CACHE_ERR_ACTIVE_ENTRY_DISPOSED, CACHE_ERR_DISPOSED } from '$libs/cache';
|
||||
export {
|
||||
CACHE_ACTIVE_ENTRY_EVENT_INVALIDATE,
|
||||
CACHE_ACTIVE_ENTRY_EVENT_LOAD,
|
||||
CACHE_ACTIVE_ENTRY_EVENT_REFRESH,
|
||||
CACHE_ACTIVE_ENTRY_EVENT_SET,
|
||||
CACHE_ACTIVE_ENTRY_EVENTS,
|
||||
CACHE_ACTIVE_DIAGNOSTIC_EVENTS,
|
||||
CACHE_ACTIVE_LOG_MESSAGE_OPERATION_FAILED,
|
||||
CACHE_ACTIVE_STATUS_DEGRADED,
|
||||
CACHE_ACTIVE_STATUS_ERROR,
|
||||
CACHE_ACTIVE_STATUS_IDLE,
|
||||
CACHE_ACTIVE_STATUS_LOADING,
|
||||
CACHE_ACTIVE_STATUS_REFRESHING,
|
||||
CACHE_ACTIVE_STATUS_STALE,
|
||||
CACHE_ACTIVE_STATUS_SUCCESS,
|
||||
CACHE_ACTIVE_STATUSES,
|
||||
CACHE_AUTO_INVALIDATE_NONE,
|
||||
CACHE_AUTO_INVALIDATE_STANDARD,
|
||||
CACHE_AUTO_INVALIDATE_TENANT_SWITCHED,
|
||||
CACHE_AUTO_INVALIDATE_USER_IDENTITY_CHANGE,
|
||||
CACHE_ERROR_MSG_ACTIVE_ENTRY_DISPOSED,
|
||||
CACHE_ERROR_MSG_DISPOSED_SUFFIX,
|
||||
CACHE_LOG_MESSAGE_ADAPTER_ERROR,
|
||||
CACHE_LOG_MESSAGE_BY_EVENT,
|
||||
CACHE_LOG_MESSAGE_DELETE,
|
||||
CACHE_LOG_MESSAGE_EVICTION,
|
||||
CACHE_LOG_MESSAGE_HIT,
|
||||
CACHE_LOG_MESSAGE_INVALIDATE,
|
||||
CACHE_LOG_MESSAGE_MISS,
|
||||
CACHE_LOG_MESSAGE_REFRESH_ERROR,
|
||||
CACHE_LOG_MESSAGE_REFRESH_START,
|
||||
CACHE_LOG_MESSAGE_REFRESH_SUCCESS,
|
||||
CACHE_LOG_MESSAGE_SCHEMA_MISMATCH,
|
||||
CACHE_LOG_MESSAGE_SCOPE_ERROR,
|
||||
CACHE_LOG_MESSAGE_SET,
|
||||
CACHE_LOG_MESSAGE_SINGLEFLIGHT_JOIN,
|
||||
CACHE_LOG_MESSAGE_STALE_HIT,
|
||||
CACHE_LOG_MESSAGE_STALE_IF_ERROR,
|
||||
CACHE_METHOD_CLEAR,
|
||||
CACHE_METHOD_ENTRY,
|
||||
CACHE_METHOD_EXPLAIN,
|
||||
CACHE_METHOD_GET,
|
||||
CACHE_METHOD_INVALIDATE,
|
||||
CACHE_METHOD_MUTATE,
|
||||
CACHE_METHOD_ON,
|
||||
CACHE_METHOD_QUERY,
|
||||
CACHE_METHOD_SET,
|
||||
CACHE_METHOD_STATS,
|
||||
CACHE_MODULE
|
||||
} from './consts.ts';
|
||||
export { createActiveCacheDiagnostics, emitActiveCacheDiagnostic } from './diagnostics.ts';
|
||||
export {
|
||||
CacheActiveEntryDisposedError,
|
||||
CacheDisposedError,
|
||||
isCacheActiveEntryDisposedError,
|
||||
isCacheDisposedError
|
||||
} from './errors.ts';
|
||||
export { disposedCacheMessage } from '$svrs/cache';
|
||||
export type {
|
||||
ActiveCacheDiagnosticEvent,
|
||||
ActiveCacheDiagnosticMeta,
|
||||
ActiveCacheDiagnostics,
|
||||
ActiveCacheDiagnosticType
|
||||
} from './diagnostics.ts';
|
||||
export type {
|
||||
ActiveCache,
|
||||
ActiveCacheEntry,
|
||||
ActiveCacheEntryEvent,
|
||||
ActiveCacheEntryListener,
|
||||
ActiveCacheEntryOptions,
|
||||
ActiveCacheEntrySnapshot,
|
||||
ActiveCacheEntryStatus,
|
||||
ActiveCacheAutoInvalidateOn,
|
||||
ActiveCacheAutoInvalidateTarget,
|
||||
ActiveCacheOptions,
|
||||
EngineCache,
|
||||
EngineCacheOptions
|
||||
} from './types.ts';
|
||||
|
||||
export {
|
||||
CACHE_ADAPTER_MEMORY,
|
||||
CACHE_ADAPTER_STORAGE,
|
||||
CACHE_DECISION_ACTION_DELETE_AND_FETCH,
|
||||
CACHE_DECISION_ACTION_FETCH,
|
||||
CACHE_DECISION_ACTION_SERVE,
|
||||
CACHE_DECISION_ACTION_SERVE_AND_REFRESH,
|
||||
CACHE_DECISION_ACTION_SKIP_CACHE,
|
||||
CACHE_DECISION_REASON_BYPASS_CACHE,
|
||||
CACHE_DECISION_REASON_CACHE_MISS,
|
||||
CACHE_DECISION_REASON_EXPIRED,
|
||||
CACHE_DECISION_REASON_FRESH,
|
||||
CACHE_DECISION_REASON_NO_STORE,
|
||||
CACHE_DECISION_REASON_PREFIX_EPOCH_CHANGED,
|
||||
CACHE_DECISION_REASON_SCHEMA_VERSION_MISMATCH,
|
||||
CACHE_DECISION_REASON_SCOPE_MISMATCH,
|
||||
CACHE_DECISION_REASON_STALE_WINDOW_VALID,
|
||||
CACHE_DECISION_REASON_TAG_EPOCH_CHANGED,
|
||||
CACHE_ENVELOPE_STATE_DEGRADED,
|
||||
CACHE_ENVELOPE_STATE_EXPIRED,
|
||||
CACHE_ENVELOPE_STATE_FRESH,
|
||||
CACHE_ENVELOPE_STATE_INVALIDATED,
|
||||
CACHE_ENVELOPE_STATE_STALE,
|
||||
CACHE_EVENT_ADAPTER_ERROR,
|
||||
CACHE_EVENT_ALL,
|
||||
CACHE_EVENT_DELETE,
|
||||
CACHE_EVENT_HIT,
|
||||
CACHE_EVENT_INVALIDATE,
|
||||
CACHE_EVENT_MISS,
|
||||
CACHE_EVENT_REFRESH_ERROR,
|
||||
CACHE_EVENT_REFRESH_START,
|
||||
CACHE_EVENT_REFRESH_SUCCESS,
|
||||
CACHE_EVENT_SCHEMA_MISMATCH,
|
||||
CACHE_EVENT_SCOPE_ERROR,
|
||||
CACHE_EVENT_SET,
|
||||
CACHE_EVENT_SINGLEFLIGHT_JOIN,
|
||||
CACHE_EVENT_STALE_HIT,
|
||||
CACHE_EVENT_STALE_IF_ERROR,
|
||||
CACHE_EVENT_EVICTION,
|
||||
CACHE_POLICY_CATALOG,
|
||||
CACHE_POLICY_IMMUTABLE,
|
||||
CACHE_POLICY_INTERACTIVE,
|
||||
CACHE_POLICY_PRIVATE_SESSION,
|
||||
CACHE_POLICY_REALTIME,
|
||||
CACHE_READ_MODE_BYPASS_CACHE,
|
||||
CACHE_READ_MODE_CACHE_FIRST,
|
||||
CACHE_READ_MODE_MUST_REVALIDATE,
|
||||
CACHE_READ_MODE_NO_STORE,
|
||||
CACHE_READ_MODE_STALE_WHILE_REVALIDATE,
|
||||
CACHE_SCOPE_ACTOR,
|
||||
CACHE_SCOPE_CUSTOM,
|
||||
CACHE_SCOPE_PERMISSION,
|
||||
CACHE_SCOPE_PUBLIC,
|
||||
CACHE_SCOPE_TENANT,
|
||||
CacheKeyError,
|
||||
CachePolicyError,
|
||||
CacheScopeError,
|
||||
createMapStorage,
|
||||
defaultCachePolicies,
|
||||
durationToMs,
|
||||
isCacheKeyError,
|
||||
isCachePolicyError,
|
||||
isCacheScopeError,
|
||||
memoryCacheAdapter,
|
||||
normalizeKey,
|
||||
normalizeKeyPrefixes,
|
||||
normalizeTag,
|
||||
normalizeTags,
|
||||
resolveScope,
|
||||
stableHash,
|
||||
stableStringify,
|
||||
storageCacheAdapter
|
||||
} from '$svrs/cache';
|
||||
|
||||
export type {
|
||||
CacheAdapter,
|
||||
CacheAdapterSetOptions,
|
||||
CacheClock,
|
||||
CacheDecision,
|
||||
CacheDecisionAction,
|
||||
CacheDecisionReason,
|
||||
CacheEnvelope,
|
||||
CacheEnvelopeState,
|
||||
CacheEvent,
|
||||
CacheEventHandler,
|
||||
CacheEventSelector,
|
||||
CacheEventType,
|
||||
CacheExplain,
|
||||
CacheKey,
|
||||
CachePolicy,
|
||||
CacheReadMode,
|
||||
CacheRuntime,
|
||||
CacheRuntimeConfig,
|
||||
CacheScopeInput,
|
||||
CacheScopeMode,
|
||||
CacheStats,
|
||||
CacheTagLike,
|
||||
CacheTagObject,
|
||||
CustomCacheScope,
|
||||
ExplainOptions,
|
||||
GetOptions,
|
||||
InvalidateOptions,
|
||||
MemoryCacheAdapter,
|
||||
MemoryCacheAdapterOptions,
|
||||
MemoryCacheEvictReason,
|
||||
MutateOptions,
|
||||
QueryOptions,
|
||||
ResolvedCachePolicy,
|
||||
ResolvedCacheScope,
|
||||
ResolvedScopeValues,
|
||||
ScopeResolver,
|
||||
SetOptions,
|
||||
StorageCacheAdapterOptions,
|
||||
StorageLike
|
||||
} from '$svrs/cache';
|
||||
@ -1,59 +0,0 @@
|
||||
import {
|
||||
CONN_ACK_REASON_CLOSED,
|
||||
CONN_ACK_REASON_REJECTED,
|
||||
CONN_ACK_REASON_TIMEOUT,
|
||||
CONN_AUTH_REASON_CLOSED,
|
||||
CONN_AUTH_REASON_NO_PROVIDER,
|
||||
CONN_AUTH_REASON_REJECTED,
|
||||
CONN_AUTH_REASON_TIMEOUT,
|
||||
CONN_AUTH_REASON_TRANSPORT_ERROR,
|
||||
CONN_FRAME_TYPE_AUTH
|
||||
} from './consts.ts';
|
||||
import type {
|
||||
ConnectionAckResult,
|
||||
ConnectionAuthPayload,
|
||||
ConnectionAuthResult,
|
||||
ConnectionOptions
|
||||
} from './types.ts';
|
||||
|
||||
export type ConnectionAuthProvider = () =>
|
||||
| ConnectionAuthPayload
|
||||
| null
|
||||
| Promise<ConnectionAuthPayload | null>;
|
||||
|
||||
export interface ResolvedConnectionAuth {
|
||||
readonly provider: ConnectionAuthProvider | null;
|
||||
readonly authType: string;
|
||||
readonly timeoutMs?: number;
|
||||
}
|
||||
|
||||
export function resolveConnectionAuth(auth: ConnectionOptions['auth']): ResolvedConnectionAuth {
|
||||
if (typeof auth === 'function') {
|
||||
return { provider: auth, authType: CONN_FRAME_TYPE_AUTH };
|
||||
}
|
||||
return {
|
||||
provider: auth?.getAuth ?? null,
|
||||
authType: auth?.authType ?? CONN_FRAME_TYPE_AUTH,
|
||||
timeoutMs: auth?.timeoutMs
|
||||
};
|
||||
}
|
||||
|
||||
export function missingConnectionAuthProvider(): ConnectionAuthResult {
|
||||
return { ok: false, reason: CONN_AUTH_REASON_NO_PROVIDER };
|
||||
}
|
||||
|
||||
export function mapConnectionAckToAuthResult(
|
||||
result: ConnectionAckResult<unknown>
|
||||
): ConnectionAuthResult {
|
||||
if (result.ok) return { ok: true };
|
||||
if (result.reason === CONN_ACK_REASON_TIMEOUT) {
|
||||
return { ok: false, reason: CONN_AUTH_REASON_TIMEOUT, error: result.error };
|
||||
}
|
||||
if (result.reason === CONN_ACK_REASON_CLOSED) {
|
||||
return { ok: false, reason: CONN_AUTH_REASON_CLOSED, error: result.error };
|
||||
}
|
||||
if (result.reason === CONN_ACK_REASON_REJECTED) {
|
||||
return { ok: false, reason: CONN_AUTH_REASON_REJECTED, error: result.error };
|
||||
}
|
||||
return { ok: false, reason: CONN_AUTH_REASON_TRANSPORT_ERROR, error: result.error };
|
||||
}
|
||||
@ -1,251 +0,0 @@
|
||||
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
|
||||
|
||||
export const CONN_MODULE = 'conn';
|
||||
export const CONN_LOGGER_SCOPE_SEPARATOR = ':';
|
||||
|
||||
export const CONN_DIAGNOSTIC_EVENTS = {
|
||||
AUTH_FAILED: 'conn.auth_failed',
|
||||
BROWSER_RECONNECT: 'conn.browser_reconnect',
|
||||
CONNECT_FAILED: 'conn.connect_failed',
|
||||
FRAME_DECODE_FAILED: 'conn.frame_decode_failed',
|
||||
FRAME_ENCODE_FAILED: 'conn.frame_encode_failed',
|
||||
HEARTBEAT_TIMEOUT: 'conn.heartbeat_timeout',
|
||||
LISTENER_THREW: 'conn.listener_threw',
|
||||
REAUTH_FAILED: 'conn.reauth_failed',
|
||||
RECONNECT_EXHAUSTED: 'conn.reconnect_exhausted',
|
||||
SEND_FAILED: 'conn.send_failed',
|
||||
SESS_CHANGED: 'conn.session_changed',
|
||||
SESS_EXPIRED: 'conn.session_expired',
|
||||
SESS_REFRESHED: 'conn.session_refreshed',
|
||||
SESS_REVOKED: 'conn.session_revoked',
|
||||
TRANSPORT_ERROR: 'conn.transport_error'
|
||||
} as const;
|
||||
|
||||
export const CONN_STATE_IDLE = 'idle';
|
||||
export const CONN_STATE_CONNECTING = 'connecting';
|
||||
export const CONN_STATE_OPEN = 'open';
|
||||
export const CONN_STATE_RECONNECTING = 'reconnecting';
|
||||
export const CONN_STATE_CLOSING = 'closing';
|
||||
export const CONN_STATE_CLOSED = 'closed';
|
||||
export const CONN_STATE_FAILED = 'failed';
|
||||
|
||||
export const CONN_STATES = [
|
||||
CONN_STATE_IDLE,
|
||||
CONN_STATE_CONNECTING,
|
||||
CONN_STATE_OPEN,
|
||||
CONN_STATE_RECONNECTING,
|
||||
CONN_STATE_CLOSING,
|
||||
CONN_STATE_CLOSED,
|
||||
CONN_STATE_FAILED
|
||||
] as const;
|
||||
|
||||
export const CONN_CHANNEL_STATE_IDLE = 'idle';
|
||||
export const CONN_CHANNEL_STATE_JOINING = 'joining';
|
||||
export const CONN_CHANNEL_STATE_JOINED = 'joined';
|
||||
export const CONN_CHANNEL_STATE_LEAVING = 'leaving';
|
||||
export const CONN_CHANNEL_STATE_LEFT = 'left';
|
||||
export const CONN_CHANNEL_STATE_FAILED = 'failed';
|
||||
|
||||
export const CONN_TRANSPORT_STATE_IDLE = 'idle';
|
||||
export const CONN_TRANSPORT_STATE_OPENING = 'opening';
|
||||
export const CONN_TRANSPORT_STATE_OPEN = 'open';
|
||||
export const CONN_TRANSPORT_STATE_CLOSING = 'closing';
|
||||
export const CONN_TRANSPORT_STATE_CLOSED = 'closed';
|
||||
|
||||
export const CONN_TRANSPORT_STATES = [
|
||||
CONN_TRANSPORT_STATE_IDLE,
|
||||
CONN_TRANSPORT_STATE_OPENING,
|
||||
CONN_TRANSPORT_STATE_OPEN,
|
||||
CONN_TRANSPORT_STATE_CLOSING,
|
||||
CONN_TRANSPORT_STATE_CLOSED
|
||||
] as const;
|
||||
|
||||
export const CONN_TRANSPORT_KIND_WEBSOCKET = 'websocket';
|
||||
export const CONN_TRANSPORT_KIND_MOCK = 'mock';
|
||||
|
||||
export const CONN_FRAME_KEY_ID = 'id';
|
||||
export const CONN_FRAME_KEY_TYPE = 'type';
|
||||
export const CONN_FRAME_KEY_TOPIC = 'topic';
|
||||
export const CONN_FRAME_KEY_PAYLOAD = 'payload';
|
||||
export const CONN_FRAME_KEY_TS = 'ts';
|
||||
export const CONN_FRAME_KEY_ACK = 'ack';
|
||||
export const CONN_FRAME_KEY_REPLY_TO = 'replyTo';
|
||||
export const CONN_FRAME_KEY_ERROR = 'error';
|
||||
|
||||
export const CONN_DEFAULT_ACK_TIMEOUT_MS = 10_000;
|
||||
export const CONN_DEFAULT_RECONNECT_MIN_DELAY_MS = 500;
|
||||
export const CONN_DEFAULT_RECONNECT_MAX_DELAY_MS = 15_000;
|
||||
export const CONN_DEFAULT_RECONNECT_FACTOR = 1.8;
|
||||
export const CONN_DEFAULT_RECONNECT_JITTER_MS = 500;
|
||||
export const CONN_DEFAULT_HEARTBEAT_INTERVAL_MS = 25_000;
|
||||
export const CONN_DEFAULT_HEARTBEAT_TIMEOUT_MS = 10_000;
|
||||
export const CONN_DEFAULT_BUFFER_MAX_MESSAGES = 100;
|
||||
export const CONN_DEFAULT_BUFFER_MAX_BYTES = 1_000_000;
|
||||
|
||||
export const CONN_DEFAULT_RECONNECT_ENABLED = true;
|
||||
export const CONN_DEFAULT_RECONNECT_ON_VISIBLE = true;
|
||||
export const CONN_DEFAULT_RECONNECT_ON_ONLINE = true;
|
||||
export const CONN_DEFAULT_HEARTBEAT_ENABLED = true;
|
||||
|
||||
export const CONN_BUFFER_POLICY_BUFFER = 'buffer';
|
||||
export const CONN_BUFFER_POLICY_DROP = 'drop';
|
||||
export const CONN_BUFFER_POLICY_FAIL = 'fail';
|
||||
|
||||
export const CONN_SEND_REASON_CLOSED = 'closed';
|
||||
export const CONN_SEND_REASON_SEND_NOT_SUPPORTED = 'send_not_supported';
|
||||
export const CONN_SEND_REASON_BUFFER_FULL = 'buffer_full';
|
||||
export const CONN_SEND_REASON_INVALID_FRAME = 'invalid_frame';
|
||||
export const CONN_SEND_REASON_SERIALIZE_FAILED = 'serialize_failed';
|
||||
export const CONN_SEND_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
|
||||
export const CONN_ACK_REASON_TIMEOUT = 'timeout';
|
||||
export const CONN_ACK_REASON_CLOSED = 'closed';
|
||||
export const CONN_ACK_REASON_REJECTED = 'rejected';
|
||||
export const CONN_ACK_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
export const CONN_ACK_REASON_INVALID_REPLY = 'invalid_reply';
|
||||
|
||||
export const CONN_AUTH_REASON_NO_PROVIDER = 'no_auth_provider';
|
||||
export const CONN_AUTH_REASON_CLOSED = 'closed';
|
||||
export const CONN_AUTH_REASON_REJECTED = 'rejected';
|
||||
export const CONN_AUTH_REASON_TIMEOUT = 'timeout';
|
||||
export const CONN_AUTH_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
|
||||
export const CONN_CONNECT_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
export const CONN_CONNECT_REASON_AUTH_FAILED = 'auth_failed';
|
||||
export const CONN_CONNECT_REASON_DISPOSED = 'disposed';
|
||||
export const CONN_CONNECT_REASON_RECONNECT_EXHAUSTED = 'reconnect_exhausted';
|
||||
|
||||
export const CONN_CHANNEL_JOIN_REASON_CLOSED = 'closed';
|
||||
export const CONN_CHANNEL_JOIN_REASON_REJECTED = 'rejected';
|
||||
export const CONN_CHANNEL_JOIN_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
|
||||
// Connection event names. Scoped with the `conn.` prefix to disambiguate
|
||||
// from cache, session or any other emitter when logs are aggregated.
|
||||
export const CONN_EVENT_STATE = 'conn.state';
|
||||
export const CONN_EVENT_MESSAGE = 'conn.message';
|
||||
export const CONN_EVENT_CHANNELS = 'conn.channels';
|
||||
export const CONN_EVENT_DISPOSED = 'conn.disposed';
|
||||
|
||||
export const CONN_METHOD_CONNECT = 'connect';
|
||||
export const CONN_METHOD_DISCONNECT = 'disconnect';
|
||||
export const CONN_METHOD_RECONNECT = 'reconnect';
|
||||
export const CONN_METHOD_SEND = 'send';
|
||||
export const CONN_METHOD_REQUEST = 'request';
|
||||
export const CONN_METHOD_CREATE_CONNECTION = 'createConnection';
|
||||
export const CONN_METHOD_CONNECTION = 'connection';
|
||||
export const CONN_METHOD_OPEN_CONNECTION = 'openConnection';
|
||||
export const CONN_METHOD_CLOSE_CONNECTION = 'closeConnection';
|
||||
export const CONN_METHOD_RECONNECT_CONNECTION = 'reconnectConnection';
|
||||
export const CONN_METHOD_OPEN_ALL = 'openAll';
|
||||
export const CONN_METHOD_CLOSE_ALL = 'closeAll';
|
||||
export const CONN_METHOD_RECONNECT_ALL = 'reconnectAll';
|
||||
export const CONN_METHOD_REAUTHENTICATE_ALL = 'reauthenticateAll';
|
||||
export const CONN_METHOD_REAUTHENTICATE = 'reauthenticate';
|
||||
export const CONN_METHOD_JOIN = 'join';
|
||||
export const CONN_METHOD_LEAVE = 'leave';
|
||||
|
||||
export const CONN_AUTO_REAUTH_NONE = 'none';
|
||||
export const CONN_AUTO_REAUTH_STANDARD = 'standard';
|
||||
export const CONN_AUTO_REAUTH_USER_IDENTITY_CHANGE = 'userIdentityChange';
|
||||
|
||||
export const CONN_FRAME_TYPE_AUTH = 'conn.auth';
|
||||
export const CONN_FRAME_TYPE_JOIN = 'conn.join';
|
||||
export const CONN_FRAME_TYPE_LEAVE = 'conn.leave';
|
||||
export const CONN_FRAME_TYPE_PING = 'conn.ping';
|
||||
export const CONN_FRAME_TYPE_PONG = 'conn.pong';
|
||||
|
||||
export const CONN_ID_PREFIX = 'conn';
|
||||
export const CONN_ID_SEPARATOR = '-';
|
||||
|
||||
export const CONN_CLOSE_REASON_DISCONNECT = 'disconnect';
|
||||
export const CONN_CLOSE_REASON_RECONNECT = 'reconnect';
|
||||
export const CONN_CLOSE_REASON_DISPOSE = 'dispose';
|
||||
export const CONN_CLOSE_REASON_HEARTBEAT_TIMEOUT = 'heartbeat_timeout';
|
||||
export const CONN_CLOSE_REASON_SESSION_EXPIRED = 'session_expired';
|
||||
export const CONN_CLOSE_REASON_AUTH_FAILED = 'auth_failed';
|
||||
|
||||
export const CONN_TIMR_KEY_RECONNECT = 'reconnect';
|
||||
export const CONN_TIMR_KEY_HEARTBEAT = 'heartbeat';
|
||||
export const CONN_TIMR_KEY_HEARTBEAT_TIMEOUT = 'heartbeat-timeout';
|
||||
export const CONN_TIMR_KEY_ACK = 'ack';
|
||||
|
||||
export const CONN_BROWSER_EVENT_OPEN = 'open';
|
||||
export const CONN_BROWSER_EVENT_MESSAGE = 'message';
|
||||
export const CONN_BROWSER_EVENT_CLOSE = 'close';
|
||||
export const CONN_BROWSER_EVENT_ERROR = 'error';
|
||||
export const CONN_BROWSER_EVENT_ONLINE = 'online';
|
||||
export const CONN_BROWSER_EVENT_VISIBILITY_CHANGE = 'visibilitychange';
|
||||
export const CONN_DOCUMENT_VISIBILITY_VISIBLE = 'visible';
|
||||
|
||||
export const CONN_WEBSOCKET_BINARY_TYPE_ARRAYBUFFER = 'arraybuffer';
|
||||
export const CONN_WEBSOCKET_READY_STATE_CONNECTING = 0;
|
||||
export const CONN_WEBSOCKET_READY_STATE_OPEN = 1;
|
||||
export const CONN_WEBSOCKET_READY_STATE_CLOSING = 2;
|
||||
export const CONN_WEBSOCKET_READY_STATE_CLOSED = 3;
|
||||
export const CONN_WEBSOCKET_CLOSE_CODE_NORMAL = 1000;
|
||||
export const CONN_WEBSOCKET_CLOSE_CODE_ABNORMAL = 1006;
|
||||
|
||||
export const CONN_MOCK_TRANSPORT_DEFAULT_CLOSE_CODE = CONN_WEBSOCKET_CLOSE_CODE_NORMAL;
|
||||
export const CONN_MOCK_TRANSPORT_DEFAULT_CLOSE_CLEAN = true;
|
||||
|
||||
// Mirror of sess lifecycle discriminants. Kept in sync with the values
|
||||
// declared in `arts/sess/consts.ts` so `session-wiring.ts` can compare
|
||||
// `change.event` strings without importing arts/sess directly.
|
||||
//
|
||||
// Long term this should be replaced by a typed union imported from
|
||||
// `$sess`; for now the values are duplicated and must move together.
|
||||
export const CONN_SESS_EVENT_REFRESHED = 'sess.lifecycle.refreshed';
|
||||
export const CONN_SESS_EVENT_ADOPTED = 'sess.lifecycle.adopted';
|
||||
export const CONN_SESS_EVENT_ADOPTED_SERVER = 'sess.lifecycle.adopted_server';
|
||||
export const CONN_SESS_EVENT_EXTERNAL_CHANGED = 'sess.lifecycle.external_changed';
|
||||
export const CONN_SESS_EVENT_EXPIRED = 'sess.lifecycle.expired';
|
||||
export const CONN_SESS_EVENT_REVOKED = 'sess.lifecycle.revoked';
|
||||
|
||||
export const CONN_LOG_MSG_LISTENER_THREW_PREFIX = 'Listener threw on ';
|
||||
export const CONN_LOG_MSG_TRANSPORT_ERROR = 'transport error';
|
||||
export const CONN_LOG_MSG_RECONNECT_EXHAUSTED = 'reconnect attempts exhausted';
|
||||
export const CONN_LOG_MSG_CONNECT_FAILED = 'connect failed';
|
||||
export const CONN_LOG_MSG_SEND_FAILED = 'send failed';
|
||||
export const CONN_LOG_MSG_FRAME_DECODE_FAILED = 'frame decode failed';
|
||||
export const CONN_LOG_MSG_FRAME_ENCODE_FAILED = 'frame encode failed';
|
||||
export const CONN_LOG_MSG_AUTH_FAILED = 'auth failed';
|
||||
export const CONN_LOG_MSG_REAUTH_FAILED = 'reauthentication failed';
|
||||
export const CONN_LOG_MSG_HEARTBEAT_TIMEOUT = 'heartbeat timeout';
|
||||
export const CONN_LOG_MSG_SESSION_CHANGED = 'session changed; reauthenticating connection';
|
||||
export const CONN_LOG_MSG_SESSION_REVOKED = 'session revoked; disconnecting connection';
|
||||
export const CONN_LOG_MSG_SESSION_EXPIRED = 'session expired; disconnecting connection';
|
||||
export const CONN_LOG_MSG_SESSION_REFRESHED = 'session refreshed; reauthenticating connection';
|
||||
export const CONN_LOG_MSG_BROWSER_RECONNECT = 'browser lifecycle reconnect requested';
|
||||
|
||||
export const CONN_ERROR_PREFIX = `[${CONN_MODULE}] `;
|
||||
|
||||
// ── 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: ';
|
||||
export const CONN_ERROR_MSG_NOT_FOUND_PREFIX = 'connection not found: ';
|
||||
export const CONN_ERROR_MSG_INVALID_NAME_PREFIX = 'invalid connection name: ';
|
||||
export const CONN_ERROR_MSG_INVALID_FRAME = 'invalid connection frame';
|
||||
export const CONN_ERROR_MSG_FRAME_MUST_BE_OBJECT = 'Connection frame must be an object';
|
||||
export const CONN_ERROR_MSG_FRAME_TYPE_MUST_BE_STRING =
|
||||
'Connection frame type must be a non-empty string';
|
||||
export const CONN_ERROR_MSG_FRAME_ID_MUST_BE_STRING = 'Connection frame id must be a string';
|
||||
export const CONN_ERROR_MSG_FRAME_TOPIC_MUST_BE_STRING = 'Connection frame topic must be a string';
|
||||
export const CONN_ERROR_MSG_FRAME_REPLY_TO_MUST_BE_STRING = 'Connection frame replyTo must be a string';
|
||||
export const CONN_ERROR_MSG_FRAME_ACK_MUST_BE_BOOLEAN = 'Connection frame ack must be a boolean';
|
||||
export const CONN_ERROR_MSG_FRAME_TS_MUST_BE_NUMBER = 'Connection frame ts must be a number';
|
||||
export const CONN_ERROR_MSG_FRAME_ERROR_MUST_BE_OBJECT = 'Connection frame error must be an object';
|
||||
export const CONN_ERROR_MSG_JSON_DECODE_FAILED = 'Connection JSON payload is invalid';
|
||||
export const CONN_ERROR_MSG_WEBSOCKET_UNAVAILABLE = 'WebSocket is not available in this runtime';
|
||||
export const CONN_ERROR_MSG_CHANNEL_ALREADY_EXISTS_PREFIX = 'channel already exists: ';
|
||||
export const CONN_ERROR_MSG_CHANNEL_NOT_FOUND_PREFIX = 'channel not found: ';
|
||||
@ -1,141 +0,0 @@
|
||||
import {
|
||||
LogLevel,
|
||||
createCatalogDiagnostics,
|
||||
type DiagnosticCatalog,
|
||||
type DiagnosticEvent,
|
||||
type Diagnostics
|
||||
} from '$libs/logr';
|
||||
import {
|
||||
CONN_DIAGNOSTIC_EVENTS,
|
||||
CONN_EVENT_MESSAGE,
|
||||
CONN_MODULE,
|
||||
CONN_LOG_MSG_AUTH_FAILED,
|
||||
CONN_LOG_MSG_BROWSER_RECONNECT,
|
||||
CONN_LOG_MSG_CONNECT_FAILED,
|
||||
CONN_LOG_MSG_FRAME_DECODE_FAILED,
|
||||
CONN_LOG_MSG_FRAME_ENCODE_FAILED,
|
||||
CONN_LOG_MSG_HEARTBEAT_TIMEOUT,
|
||||
CONN_LOG_MSG_REAUTH_FAILED,
|
||||
CONN_LOG_MSG_RECONNECT_EXHAUSTED,
|
||||
CONN_LOG_MSG_SEND_FAILED,
|
||||
CONN_LOG_MSG_SESSION_CHANGED,
|
||||
CONN_LOG_MSG_SESSION_EXPIRED,
|
||||
CONN_LOG_MSG_SESSION_REFRESHED,
|
||||
CONN_LOG_MSG_SESSION_REVOKED,
|
||||
CONN_LOG_MSG_TRANSPORT_ERROR
|
||||
} from './consts.ts';
|
||||
import { listenerThrewMessage } from './helpers.ts';
|
||||
import type { Logger } from '$libs/logr';
|
||||
|
||||
export type ConnectionDiagnosticType =
|
||||
(typeof CONN_DIAGNOSTIC_EVENTS)[keyof typeof CONN_DIAGNOSTIC_EVENTS];
|
||||
|
||||
export type ConnectionDiagnosticEvent = DiagnosticEvent<ConnectionDiagnosticType>;
|
||||
export type ConnectionDiagnostics = Diagnostics<ConnectionDiagnosticEvent>;
|
||||
|
||||
const CONN_DIAGNOSTIC_LOGS: DiagnosticCatalog<ConnectionDiagnosticEvent> = {
|
||||
[CONN_DIAGNOSTIC_EVENTS.AUTH_FAILED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONN_LOG_MSG_AUTH_FAILED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.BROWSER_RECONNECT]: {
|
||||
level: LogLevel.DEBUG,
|
||||
message: CONN_LOG_MSG_BROWSER_RECONNECT
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.CONNECT_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONN_LOG_MSG_CONNECT_FAILED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.FRAME_DECODE_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONN_LOG_MSG_FRAME_DECODE_FAILED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.FRAME_ENCODE_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONN_LOG_MSG_FRAME_ENCODE_FAILED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.HEARTBEAT_TIMEOUT]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONN_LOG_MSG_HEARTBEAT_TIMEOUT
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.LISTENER_THREW]: (event) => ({
|
||||
level: LogLevel.ERROR,
|
||||
message: listenerThrewMessage(listenerEventName(event.meta))
|
||||
}),
|
||||
[CONN_DIAGNOSTIC_EVENTS.REAUTH_FAILED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONN_LOG_MSG_REAUTH_FAILED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.RECONNECT_EXHAUSTED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONN_LOG_MSG_RECONNECT_EXHAUSTED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.SEND_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONN_LOG_MSG_SEND_FAILED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.SESS_CHANGED]: {
|
||||
level: LogLevel.DEBUG,
|
||||
message: CONN_LOG_MSG_SESSION_CHANGED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.SESS_EXPIRED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONN_LOG_MSG_SESSION_EXPIRED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.SESS_REFRESHED]: {
|
||||
level: LogLevel.DEBUG,
|
||||
message: CONN_LOG_MSG_SESSION_REFRESHED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.SESS_REVOKED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONN_LOG_MSG_SESSION_REVOKED
|
||||
},
|
||||
[CONN_DIAGNOSTIC_EVENTS.TRANSPORT_ERROR]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONN_LOG_MSG_TRANSPORT_ERROR
|
||||
}
|
||||
};
|
||||
|
||||
function listenerEventName(meta: unknown): string {
|
||||
if (typeof meta !== 'object' || meta === null) return CONN_EVENT_MESSAGE;
|
||||
if (!('event' in meta)) return CONN_EVENT_MESSAGE;
|
||||
const event = meta.event;
|
||||
return typeof event === 'string' && event.length > 0 ? event : CONN_EVENT_MESSAGE;
|
||||
}
|
||||
|
||||
export function createConnectionDiagnostics(input: {
|
||||
readonly logger?: Logger;
|
||||
readonly scope: string;
|
||||
}): ConnectionDiagnostics {
|
||||
return createCatalogDiagnostics({
|
||||
logger: input.logger,
|
||||
defaultCategory: input.scope,
|
||||
catalog: CONN_DIAGNOSTIC_LOGS
|
||||
});
|
||||
}
|
||||
|
||||
export function emitConnectionDiagnostic(
|
||||
diagnostics: ConnectionDiagnostics,
|
||||
type: ConnectionDiagnosticType,
|
||||
meta?: unknown
|
||||
): void {
|
||||
diagnostics.emit({
|
||||
artifact: CONN_MODULE,
|
||||
type,
|
||||
meta
|
||||
});
|
||||
}
|
||||
|
||||
export function emitScopedConnectionDiagnostic(
|
||||
diagnostics: ConnectionDiagnostics,
|
||||
scope: string,
|
||||
type: ConnectionDiagnosticType,
|
||||
meta?: unknown
|
||||
): void {
|
||||
diagnostics.emit({
|
||||
artifact: CONN_MODULE,
|
||||
scope,
|
||||
type,
|
||||
meta
|
||||
});
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
import { CodeError } from '$libs/errs';
|
||||
import {
|
||||
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';
|
||||
|
||||
export class ConnDisposedError extends CodeError {
|
||||
constructor(message: string, options?: { cause?: unknown }) {
|
||||
super(CONN_ERR_DISPOSED, { message, cause: options?.cause });
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnConnectionAlreadyExistsError extends CodeError {
|
||||
readonly connection: string;
|
||||
constructor(message: string, connection: string) {
|
||||
super(CONN_ERR_CONNECTION_ALREADY_EXISTS, { message });
|
||||
this.connection = connection;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnConnectionNotFoundError extends CodeError {
|
||||
readonly connection: string;
|
||||
constructor(message: string, connection: string) {
|
||||
super(CONN_ERR_CONNECTION_NOT_FOUND, { message });
|
||||
this.connection = connection;
|
||||
}
|
||||
}
|
||||
|
||||
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 CodeError {
|
||||
constructor(message: string, options?: { cause?: unknown }) {
|
||||
super(CONN_ERR_INVALID_FRAME, { message, cause: options?.cause });
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnChannelAlreadyExistsError extends CodeError {
|
||||
readonly channel: string;
|
||||
constructor(message: string, channel: string) {
|
||||
super(CONN_ERR_CHANNEL_ALREADY_EXISTS, { message });
|
||||
this.channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnChannelNotFoundError extends CodeError {
|
||||
readonly channel: string;
|
||||
constructor(message: string, channel: string) {
|
||||
super(CONN_ERR_CHANNEL_NOT_FOUND, { message });
|
||||
this.channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
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 ConnDisposedError;
|
||||
}
|
||||
|
||||
export function isConnConnectionAlreadyExistsError(
|
||||
value: unknown
|
||||
): value is ConnConnectionAlreadyExistsError {
|
||||
return value instanceof ConnConnectionAlreadyExistsError;
|
||||
}
|
||||
|
||||
export function isConnConnectionNotFoundError(
|
||||
value: unknown
|
||||
): value is ConnConnectionNotFoundError {
|
||||
return value instanceof ConnConnectionNotFoundError;
|
||||
}
|
||||
|
||||
export function isConnInvalidConnectionNameError(
|
||||
value: unknown
|
||||
): value is ConnInvalidConnectionNameError {
|
||||
return value instanceof ConnInvalidConnectionNameError;
|
||||
}
|
||||
|
||||
export function isConnInvalidFrameError(value: unknown): value is ConnInvalidFrameError {
|
||||
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 ConnWebSocketUnavailableError;
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
import {
|
||||
CONN_ERROR_MSG_ALREADY_EXISTS_PREFIX,
|
||||
CONN_ERROR_MSG_CHANNEL_ALREADY_EXISTS_PREFIX,
|
||||
CONN_ERROR_MSG_CHANNEL_NOT_FOUND_PREFIX,
|
||||
CONN_ERROR_MSG_DISPOSED_SUFFIX,
|
||||
CONN_ERROR_MSG_INVALID_NAME_PREFIX,
|
||||
CONN_ERROR_MSG_NOT_FOUND_PREFIX,
|
||||
CONN_ERROR_PREFIX,
|
||||
CONN_ID_PREFIX,
|
||||
CONN_ID_SEPARATOR,
|
||||
CONN_MODULE,
|
||||
CONN_LOGGER_SCOPE_SEPARATOR,
|
||||
CONN_LOG_MSG_LISTENER_THREW_PREFIX
|
||||
} from './consts.ts';
|
||||
|
||||
export function disposedErrorMessage(method: string): string {
|
||||
return `${CONN_ERROR_PREFIX}${method}${CONN_ERROR_MSG_DISPOSED_SUFFIX}`;
|
||||
}
|
||||
|
||||
export function alreadyExistsErrorMessage(name: string): string {
|
||||
return `${CONN_ERROR_PREFIX}${CONN_ERROR_MSG_ALREADY_EXISTS_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function notFoundErrorMessage(name: string): string {
|
||||
return `${CONN_ERROR_PREFIX}${CONN_ERROR_MSG_NOT_FOUND_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function invalidNameErrorMessage(name: unknown): string {
|
||||
return `${CONN_ERROR_PREFIX}${CONN_ERROR_MSG_INVALID_NAME_PREFIX}${String(name)}`;
|
||||
}
|
||||
|
||||
export function channelAlreadyExistsErrorMessage(name: string): string {
|
||||
return `${CONN_ERROR_PREFIX}${CONN_ERROR_MSG_CHANNEL_ALREADY_EXISTS_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function channelNotFoundErrorMessage(name: string): string {
|
||||
return `${CONN_ERROR_PREFIX}${CONN_ERROR_MSG_CHANNEL_NOT_FOUND_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function listenerThrewMessage(event: string): string {
|
||||
return `${CONN_LOG_MSG_LISTENER_THREW_PREFIX}${event}`;
|
||||
}
|
||||
|
||||
export function loggerScope(name: string, topic?: string): string {
|
||||
return topic === undefined
|
||||
? `${CONN_MODULE}${CONN_LOGGER_SCOPE_SEPARATOR}${name}`
|
||||
: `${CONN_MODULE}${CONN_LOGGER_SCOPE_SEPARATOR}${name}${CONN_LOGGER_SCOPE_SEPARATOR}${topic}`;
|
||||
}
|
||||
|
||||
export function timerKey(connection: string, kind: string, id?: string): string {
|
||||
return id === undefined
|
||||
? `${CONN_MODULE}${CONN_LOGGER_SCOPE_SEPARATOR}${connection}${CONN_LOGGER_SCOPE_SEPARATOR}${kind}`
|
||||
: `${CONN_MODULE}${CONN_LOGGER_SCOPE_SEPARATOR}${connection}${CONN_LOGGER_SCOPE_SEPARATOR}${kind}${CONN_LOGGER_SCOPE_SEPARATOR}${id}`;
|
||||
}
|
||||
|
||||
export function createConnectionIdFactory(connectionName: string): () => string {
|
||||
let next = 0;
|
||||
return () => {
|
||||
next += 1;
|
||||
return [CONN_ID_PREFIX, connectionName, String(next)].join(CONN_ID_SEPARATOR);
|
||||
};
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
import {
|
||||
CONN_FRAME_KEY_ACK,
|
||||
CONN_FRAME_KEY_ERROR,
|
||||
CONN_FRAME_KEY_ID,
|
||||
CONN_FRAME_KEY_PAYLOAD,
|
||||
CONN_FRAME_KEY_REPLY_TO,
|
||||
CONN_FRAME_KEY_TOPIC,
|
||||
CONN_FRAME_KEY_TS,
|
||||
CONN_FRAME_KEY_TYPE,
|
||||
CONN_ERROR_MSG_FRAME_ACK_MUST_BE_BOOLEAN,
|
||||
CONN_ERROR_MSG_FRAME_ERROR_MUST_BE_OBJECT,
|
||||
CONN_ERROR_MSG_FRAME_ID_MUST_BE_STRING,
|
||||
CONN_ERROR_MSG_FRAME_MUST_BE_OBJECT,
|
||||
CONN_ERROR_MSG_FRAME_REPLY_TO_MUST_BE_STRING,
|
||||
CONN_ERROR_MSG_FRAME_TOPIC_MUST_BE_STRING,
|
||||
CONN_ERROR_MSG_FRAME_TS_MUST_BE_NUMBER,
|
||||
CONN_ERROR_MSG_FRAME_TYPE_MUST_BE_STRING
|
||||
} from './consts.ts';
|
||||
import { ConnInvalidFrameError } from './errors.ts';
|
||||
import type { ConnectionFrame } from './types.ts';
|
||||
|
||||
export function assertConnectionFrame(value: unknown): asserts value is ConnectionFrame {
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_MUST_BE_OBJECT);
|
||||
}
|
||||
const frame = value as Record<string, unknown>;
|
||||
if (typeof frame[CONN_FRAME_KEY_TYPE] !== 'string' || frame[CONN_FRAME_KEY_TYPE].length === 0) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_TYPE_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONN_FRAME_KEY_ID in frame &&
|
||||
frame[CONN_FRAME_KEY_ID] !== undefined &&
|
||||
typeof frame[CONN_FRAME_KEY_ID] !== 'string'
|
||||
) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_ID_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONN_FRAME_KEY_TOPIC in frame &&
|
||||
frame[CONN_FRAME_KEY_TOPIC] !== undefined &&
|
||||
typeof frame[CONN_FRAME_KEY_TOPIC] !== 'string'
|
||||
) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_TOPIC_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONN_FRAME_KEY_REPLY_TO in frame &&
|
||||
frame[CONN_FRAME_KEY_REPLY_TO] !== undefined &&
|
||||
typeof frame[CONN_FRAME_KEY_REPLY_TO] !== 'string'
|
||||
) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_REPLY_TO_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONN_FRAME_KEY_ACK in frame &&
|
||||
frame[CONN_FRAME_KEY_ACK] !== undefined &&
|
||||
typeof frame[CONN_FRAME_KEY_ACK] !== 'boolean'
|
||||
) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_ACK_MUST_BE_BOOLEAN);
|
||||
}
|
||||
if (
|
||||
CONN_FRAME_KEY_TS in frame &&
|
||||
frame[CONN_FRAME_KEY_TS] !== undefined &&
|
||||
typeof frame[CONN_FRAME_KEY_TS] !== 'number'
|
||||
) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_TS_MUST_BE_NUMBER);
|
||||
}
|
||||
if (
|
||||
CONN_FRAME_KEY_ERROR in frame &&
|
||||
frame[CONN_FRAME_KEY_ERROR] !== undefined &&
|
||||
(frame[CONN_FRAME_KEY_ERROR] === null || typeof frame[CONN_FRAME_KEY_ERROR] !== 'object')
|
||||
) {
|
||||
throw new ConnInvalidFrameError(CONN_ERROR_MSG_FRAME_ERROR_MUST_BE_OBJECT);
|
||||
}
|
||||
}
|
||||
|
||||
export function createFrame(input: ConnectionFrame): ConnectionFrame {
|
||||
assertConnectionFrame(input);
|
||||
const frame: Record<string, unknown> = {
|
||||
[CONN_FRAME_KEY_TYPE]: input.type,
|
||||
[CONN_FRAME_KEY_PAYLOAD]: input.payload
|
||||
};
|
||||
if (input.id !== undefined) frame[CONN_FRAME_KEY_ID] = input.id;
|
||||
if (input.topic !== undefined) frame[CONN_FRAME_KEY_TOPIC] = input.topic;
|
||||
if (input.ts !== undefined) frame[CONN_FRAME_KEY_TS] = input.ts;
|
||||
if (input.ack !== undefined) frame[CONN_FRAME_KEY_ACK] = input.ack;
|
||||
if (input.replyTo !== undefined) frame[CONN_FRAME_KEY_REPLY_TO] = input.replyTo;
|
||||
if (input.error !== undefined) frame[CONN_FRAME_KEY_ERROR] = input.error;
|
||||
return frame as unknown as ConnectionFrame;
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
import {
|
||||
AAPP_EVENT_USER_IDENTITY_CHANGED,
|
||||
APP_EVENT_USER_IDENTITY_CHANGED,
|
||||
type AppEventBus
|
||||
} from '$libs/aapp/events';
|
||||
} from '$libs/active-app/events';
|
||||
import type { ConnectionSessionSource } from './types.ts';
|
||||
|
||||
export function createBusSessionSource(bus: AppEventBus): ConnectionSessionSource {
|
||||
return {
|
||||
onChange(listener) {
|
||||
const subscription = bus.on(AAPP_EVENT_USER_IDENTITY_CHANGED, (event) => {
|
||||
const subscription = bus.on(APP_EVENT_USER_IDENTITY_CHANGED, (event) => {
|
||||
listener(event.payload);
|
||||
});
|
||||
return () => {
|
||||
@ -0,0 +1,59 @@
|
||||
import {
|
||||
CONNECTION_ACK_REASON_CLOSED,
|
||||
CONNECTION_ACK_REASON_REJECTED,
|
||||
CONNECTION_ACK_REASON_TIMEOUT,
|
||||
CONNECTION_AUTH_REASON_CLOSED,
|
||||
CONNECTION_AUTH_REASON_NO_PROVIDER,
|
||||
CONNECTION_AUTH_REASON_REJECTED,
|
||||
CONNECTION_AUTH_REASON_TIMEOUT,
|
||||
CONNECTION_AUTH_REASON_TRANSPORT_ERROR,
|
||||
CONNECTION_FRAME_TYPE_AUTH
|
||||
} from './consts.ts';
|
||||
import type {
|
||||
ConnectionAckResult,
|
||||
ConnectionAuthPayload,
|
||||
ConnectionAuthResult,
|
||||
ConnectionOptions
|
||||
} from './types.ts';
|
||||
|
||||
export type ConnectionAuthProvider = () =>
|
||||
| ConnectionAuthPayload
|
||||
| null
|
||||
| Promise<ConnectionAuthPayload | null>;
|
||||
|
||||
export interface ResolvedConnectionAuth {
|
||||
readonly provider: ConnectionAuthProvider | null;
|
||||
readonly authType: string;
|
||||
readonly timeoutMs?: number;
|
||||
}
|
||||
|
||||
export function resolveConnectionAuth(auth: ConnectionOptions['auth']): ResolvedConnectionAuth {
|
||||
if (typeof auth === 'function') {
|
||||
return { provider: auth, authType: CONNECTION_FRAME_TYPE_AUTH };
|
||||
}
|
||||
return {
|
||||
provider: auth?.getAuth ?? null,
|
||||
authType: auth?.authType ?? CONNECTION_FRAME_TYPE_AUTH,
|
||||
timeoutMs: auth?.timeoutMs
|
||||
};
|
||||
}
|
||||
|
||||
export function missingConnectionAuthProvider(): ConnectionAuthResult {
|
||||
return { ok: false, reason: CONNECTION_AUTH_REASON_NO_PROVIDER };
|
||||
}
|
||||
|
||||
export function mapConnectionAckToAuthResult(
|
||||
result: ConnectionAckResult<unknown>
|
||||
): ConnectionAuthResult {
|
||||
if (result.ok) return { ok: true };
|
||||
if (result.reason === CONNECTION_ACK_REASON_TIMEOUT) {
|
||||
return { ok: false, reason: CONNECTION_AUTH_REASON_TIMEOUT, error: result.error };
|
||||
}
|
||||
if (result.reason === CONNECTION_ACK_REASON_CLOSED) {
|
||||
return { ok: false, reason: CONNECTION_AUTH_REASON_CLOSED, error: result.error };
|
||||
}
|
||||
if (result.reason === CONNECTION_ACK_REASON_REJECTED) {
|
||||
return { ok: false, reason: CONNECTION_AUTH_REASON_REJECTED, error: result.error };
|
||||
}
|
||||
return { ok: false, reason: CONNECTION_AUTH_REASON_TRANSPORT_ERROR, error: result.error };
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import type { TimerScheduler } from '$libs/timers';
|
||||
import type { TimerScheduler } from '$libs/timer';
|
||||
import { timerKey } from './helpers.ts';
|
||||
|
||||
export interface ConnectionTimerControls {
|
||||
@ -0,0 +1,251 @@
|
||||
import { errCode, moduleSeed, type ErrCode, type ModuleSeed } from '$libs/errs';
|
||||
|
||||
export const CONNECTION_MODULE = 'connection';
|
||||
export const CONNECTION_LOGGER_SCOPE_SEPARATOR = ':';
|
||||
|
||||
export const CONNECTION_DIAGNOSTIC_EVENTS = {
|
||||
AUTH_FAILED: 'connection.auth_failed',
|
||||
BROWSER_RECONNECT: 'connection.browser_reconnect',
|
||||
CONNECT_FAILED: 'connection.connect_failed',
|
||||
FRAME_DECODE_FAILED: 'connection.frame_decode_failed',
|
||||
FRAME_ENCODE_FAILED: 'connection.frame_encode_failed',
|
||||
HEARTBEAT_TIMEOUT: 'connection.heartbeat_timeout',
|
||||
LISTENER_THREW: 'connection.listener_threw',
|
||||
REAUTH_FAILED: 'connection.reauth_failed',
|
||||
RECONNECT_EXHAUSTED: 'connection.reconnect_exhausted',
|
||||
SEND_FAILED: 'connection.send_failed',
|
||||
SESSION_CHANGED: 'connection.session_changed',
|
||||
SESSION_EXPIRED: 'connection.session_expired',
|
||||
SESSION_REFRESHED: 'connection.session_refreshed',
|
||||
SESSION_REVOKED: 'connection.session_revoked',
|
||||
TRANSPORT_ERROR: 'connection.transport_error'
|
||||
} as const;
|
||||
|
||||
export const CONNECTION_STATE_IDLE = 'idle';
|
||||
export const CONNECTION_STATE_CONNECTING = 'connecting';
|
||||
export const CONNECTION_STATE_OPEN = 'open';
|
||||
export const CONNECTION_STATE_RECONNECTING = 'reconnecting';
|
||||
export const CONNECTION_STATE_CLOSING = 'closing';
|
||||
export const CONNECTION_STATE_CLOSED = 'closed';
|
||||
export const CONNECTION_STATE_FAILED = 'failed';
|
||||
|
||||
export const CONNECTION_STATES = [
|
||||
CONNECTION_STATE_IDLE,
|
||||
CONNECTION_STATE_CONNECTING,
|
||||
CONNECTION_STATE_OPEN,
|
||||
CONNECTION_STATE_RECONNECTING,
|
||||
CONNECTION_STATE_CLOSING,
|
||||
CONNECTION_STATE_CLOSED,
|
||||
CONNECTION_STATE_FAILED
|
||||
] as const;
|
||||
|
||||
export const CONNECTION_CHANNEL_STATE_IDLE = 'idle';
|
||||
export const CONNECTION_CHANNEL_STATE_JOINING = 'joining';
|
||||
export const CONNECTION_CHANNEL_STATE_JOINED = 'joined';
|
||||
export const CONNECTION_CHANNEL_STATE_LEAVING = 'leaving';
|
||||
export const CONNECTION_CHANNEL_STATE_LEFT = 'left';
|
||||
export const CONNECTION_CHANNEL_STATE_FAILED = 'failed';
|
||||
|
||||
export const CONNECTION_TRANSPORT_STATE_IDLE = 'idle';
|
||||
export const CONNECTION_TRANSPORT_STATE_OPENING = 'opening';
|
||||
export const CONNECTION_TRANSPORT_STATE_OPEN = 'open';
|
||||
export const CONNECTION_TRANSPORT_STATE_CLOSING = 'closing';
|
||||
export const CONNECTION_TRANSPORT_STATE_CLOSED = 'closed';
|
||||
|
||||
export const CONNECTION_TRANSPORT_STATES = [
|
||||
CONNECTION_TRANSPORT_STATE_IDLE,
|
||||
CONNECTION_TRANSPORT_STATE_OPENING,
|
||||
CONNECTION_TRANSPORT_STATE_OPEN,
|
||||
CONNECTION_TRANSPORT_STATE_CLOSING,
|
||||
CONNECTION_TRANSPORT_STATE_CLOSED
|
||||
] as const;
|
||||
|
||||
export const CONNECTION_TRANSPORT_KIND_WEBSOCKET = 'websocket';
|
||||
export const CONNECTION_TRANSPORT_KIND_MOCK = 'mock';
|
||||
|
||||
export const CONNECTION_FRAME_KEY_ID = 'id';
|
||||
export const CONNECTION_FRAME_KEY_TYPE = 'type';
|
||||
export const CONNECTION_FRAME_KEY_TOPIC = 'topic';
|
||||
export const CONNECTION_FRAME_KEY_PAYLOAD = 'payload';
|
||||
export const CONNECTION_FRAME_KEY_TS = 'ts';
|
||||
export const CONNECTION_FRAME_KEY_ACK = 'ack';
|
||||
export const CONNECTION_FRAME_KEY_REPLY_TO = 'replyTo';
|
||||
export const CONNECTION_FRAME_KEY_ERROR = 'error';
|
||||
|
||||
export const CONNECTION_DEFAULT_ACK_TIMEOUT_MS = 10_000;
|
||||
export const CONNECTION_DEFAULT_RECONNECT_MIN_DELAY_MS = 500;
|
||||
export const CONNECTION_DEFAULT_RECONNECT_MAX_DELAY_MS = 15_000;
|
||||
export const CONNECTION_DEFAULT_RECONNECT_FACTOR = 1.8;
|
||||
export const CONNECTION_DEFAULT_RECONNECT_JITTER_MS = 500;
|
||||
export const CONNECTION_DEFAULT_HEARTBEAT_INTERVAL_MS = 25_000;
|
||||
export const CONNECTION_DEFAULT_HEARTBEAT_TIMEOUT_MS = 10_000;
|
||||
export const CONNECTION_DEFAULT_BUFFER_MAX_MESSAGES = 100;
|
||||
export const CONNECTION_DEFAULT_BUFFER_MAX_BYTES = 1_000_000;
|
||||
|
||||
export const CONNECTION_DEFAULT_RECONNECT_ENABLED = true;
|
||||
export const CONNECTION_DEFAULT_RECONNECT_ON_VISIBLE = true;
|
||||
export const CONNECTION_DEFAULT_RECONNECT_ON_ONLINE = true;
|
||||
export const CONNECTION_DEFAULT_HEARTBEAT_ENABLED = true;
|
||||
|
||||
export const CONNECTION_BUFFER_POLICY_BUFFER = 'buffer';
|
||||
export const CONNECTION_BUFFER_POLICY_DROP = 'drop';
|
||||
export const CONNECTION_BUFFER_POLICY_FAIL = 'fail';
|
||||
|
||||
export const CONNECTION_SEND_REASON_CLOSED = 'closed';
|
||||
export const CONNECTION_SEND_REASON_SEND_NOT_SUPPORTED = 'send_not_supported';
|
||||
export const CONNECTION_SEND_REASON_BUFFER_FULL = 'buffer_full';
|
||||
export const CONNECTION_SEND_REASON_INVALID_FRAME = 'invalid_frame';
|
||||
export const CONNECTION_SEND_REASON_SERIALIZE_FAILED = 'serialize_failed';
|
||||
export const CONNECTION_SEND_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
|
||||
export const CONNECTION_ACK_REASON_TIMEOUT = 'timeout';
|
||||
export const CONNECTION_ACK_REASON_CLOSED = 'closed';
|
||||
export const CONNECTION_ACK_REASON_REJECTED = 'rejected';
|
||||
export const CONNECTION_ACK_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
export const CONNECTION_ACK_REASON_INVALID_REPLY = 'invalid_reply';
|
||||
|
||||
export const CONNECTION_AUTH_REASON_NO_PROVIDER = 'no_auth_provider';
|
||||
export const CONNECTION_AUTH_REASON_CLOSED = 'closed';
|
||||
export const CONNECTION_AUTH_REASON_REJECTED = 'rejected';
|
||||
export const CONNECTION_AUTH_REASON_TIMEOUT = 'timeout';
|
||||
export const CONNECTION_AUTH_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
|
||||
export const CONNECTION_CONNECT_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
export const CONNECTION_CONNECT_REASON_AUTH_FAILED = 'auth_failed';
|
||||
export const CONNECTION_CONNECT_REASON_DISPOSED = 'disposed';
|
||||
export const CONNECTION_CONNECT_REASON_RECONNECT_EXHAUSTED = 'reconnect_exhausted';
|
||||
|
||||
export const CONNECTION_CHANNEL_JOIN_REASON_CLOSED = 'closed';
|
||||
export const CONNECTION_CHANNEL_JOIN_REASON_REJECTED = 'rejected';
|
||||
export const CONNECTION_CHANNEL_JOIN_REASON_TRANSPORT_ERROR = 'transport_error';
|
||||
|
||||
// Connection event names. Scoped with the `conn.` prefix to disambiguate
|
||||
// from cache, session or any other emitter when logs are aggregated.
|
||||
export const CONNECTION_EVENT_STATE = 'connection.state';
|
||||
export const CONNECTION_EVENT_MESSAGE = 'connection.message';
|
||||
export const CONNECTION_EVENT_CHANNELS = 'connection.channels';
|
||||
export const CONNECTION_EVENT_DISPOSED = 'connection.disposed';
|
||||
|
||||
export const CONNECTION_METHOD_CONNECT = 'connect';
|
||||
export const CONNECTION_METHOD_DISCONNECT = 'disconnect';
|
||||
export const CONNECTION_METHOD_RECONNECT = 'reconnect';
|
||||
export const CONNECTION_METHOD_SEND = 'send';
|
||||
export const CONNECTION_METHOD_REQUEST = 'request';
|
||||
export const CONNECTION_METHOD_CREATE_CONNECTION = 'createConnection';
|
||||
export const CONNECTION_METHOD_CONNECTION = 'connection';
|
||||
export const CONNECTION_METHOD_OPEN_CONNECTION = 'openConnection';
|
||||
export const CONNECTION_METHOD_CLOSE_CONNECTION = 'closeConnection';
|
||||
export const CONNECTION_METHOD_RECONNECT_CONNECTION = 'reconnectConnection';
|
||||
export const CONNECTION_METHOD_OPEN_ALL = 'openAll';
|
||||
export const CONNECTION_METHOD_CLOSE_ALL = 'closeAll';
|
||||
export const CONNECTION_METHOD_RECONNECT_ALL = 'reconnectAll';
|
||||
export const CONNECTION_METHOD_REAUTHENTICATE_ALL = 'reauthenticateAll';
|
||||
export const CONNECTION_METHOD_REAUTHENTICATE = 'reauthenticate';
|
||||
export const CONNECTION_METHOD_JOIN = 'join';
|
||||
export const CONNECTION_METHOD_LEAVE = 'leave';
|
||||
|
||||
export const CONNECTION_AUTO_REAUTH_NONE = 'none';
|
||||
export const CONNECTION_AUTO_REAUTH_STANDARD = 'standard';
|
||||
export const CONNECTION_AUTO_REAUTH_USER_IDENTITY_CHANGE = 'userIdentityChange';
|
||||
|
||||
export const CONNECTION_FRAME_TYPE_AUTH = 'connection.auth';
|
||||
export const CONNECTION_FRAME_TYPE_JOIN = 'connection.join';
|
||||
export const CONNECTION_FRAME_TYPE_LEAVE = 'connection.leave';
|
||||
export const CONNECTION_FRAME_TYPE_PING = 'connection.ping';
|
||||
export const CONNECTION_FRAME_TYPE_PONG = 'connection.pong';
|
||||
|
||||
export const CONNECTION_ID_PREFIX = 'connection';
|
||||
export const CONNECTION_ID_SEPARATOR = '-';
|
||||
|
||||
export const CONNECTION_CLOSE_REASON_DISCONNECT = 'disconnect';
|
||||
export const CONNECTION_CLOSE_REASON_RECONNECT = 'reconnect';
|
||||
export const CONNECTION_CLOSE_REASON_DISPOSE = 'dispose';
|
||||
export const CONNECTION_CLOSE_REASON_HEARTBEAT_TIMEOUT = 'heartbeat_timeout';
|
||||
export const CONNECTION_CLOSE_REASON_SESSION_EXPIRED = 'session_expired';
|
||||
export const CONNECTION_CLOSE_REASON_AUTH_FAILED = 'auth_failed';
|
||||
|
||||
export const CONNECTION_TIMER_KEY_RECONNECT = 'reconnect';
|
||||
export const CONNECTION_TIMER_KEY_HEARTBEAT = 'heartbeat';
|
||||
export const CONNECTION_TIMER_KEY_HEARTBEAT_TIMEOUT = 'heartbeat-timeout';
|
||||
export const CONNECTION_TIMER_KEY_ACK = 'ack';
|
||||
|
||||
export const CONNECTION_BROWSER_EVENT_OPEN = 'open';
|
||||
export const CONNECTION_BROWSER_EVENT_MESSAGE = 'message';
|
||||
export const CONNECTION_BROWSER_EVENT_CLOSE = 'close';
|
||||
export const CONNECTION_BROWSER_EVENT_ERROR = 'error';
|
||||
export const CONNECTION_BROWSER_EVENT_ONLINE = 'online';
|
||||
export const CONNECTION_BROWSER_EVENT_VISIBILITY_CHANGE = 'visibilitychange';
|
||||
export const CONNECTION_DOCUMENT_VISIBILITY_VISIBLE = 'visible';
|
||||
|
||||
export const CONNECTION_WEBSOCKET_BINARY_TYPE_ARRAYBUFFER = 'arraybuffer';
|
||||
export const CONNECTION_WEBSOCKET_READY_STATE_CONNECTING = 0;
|
||||
export const CONNECTION_WEBSOCKET_READY_STATE_OPEN = 1;
|
||||
export const CONNECTION_WEBSOCKET_READY_STATE_CLOSING = 2;
|
||||
export const CONNECTION_WEBSOCKET_READY_STATE_CLOSED = 3;
|
||||
export const CONNECTION_WEBSOCKET_CLOSE_CODE_NORMAL = 1000;
|
||||
export const CONNECTION_WEBSOCKET_CLOSE_CODE_ABNORMAL = 1006;
|
||||
|
||||
export const CONNECTION_MOCK_TRANSPORT_DEFAULT_CLOSE_CODE = CONNECTION_WEBSOCKET_CLOSE_CODE_NORMAL;
|
||||
export const CONNECTION_MOCK_TRANSPORT_DEFAULT_CLOSE_CLEAN = true;
|
||||
|
||||
// Mirror of sess lifecycle discriminants. Kept in sync with the values
|
||||
// declared in `arts/sess/consts.ts` so `session-wiring.ts` can compare
|
||||
// `change.event` strings without importing arts/sess directly.
|
||||
//
|
||||
// Long term this should be replaced by a typed union imported from
|
||||
// `$session`; for now the values are duplicated and must move together.
|
||||
export const CONNECTION_SESSION_EVENT_REFRESHED = 'session.lifecycle.refreshed';
|
||||
export const CONNECTION_SESSION_EVENT_ADOPTED = 'session.lifecycle.adopted';
|
||||
export const CONNECTION_SESSION_EVENT_ADOPTED_SERVER = 'session.lifecycle.adopted_server';
|
||||
export const CONNECTION_SESSION_EVENT_EXTERNAL_CHANGED = 'session.lifecycle.external_changed';
|
||||
export const CONNECTION_SESSION_EVENT_EXPIRED = 'session.lifecycle.expired';
|
||||
export const CONNECTION_SESSION_EVENT_REVOKED = 'session.lifecycle.revoked';
|
||||
|
||||
export const CONNECTION_LOG_MSG_LISTENER_THREW_PREFIX = 'Listener threw on ';
|
||||
export const CONNECTION_LOG_MSG_TRANSPORT_ERROR = 'transport error';
|
||||
export const CONNECTION_LOG_MSG_RECONNECT_EXHAUSTED = 'reconnect attempts exhausted';
|
||||
export const CONNECTION_LOG_MSG_CONNECT_FAILED = 'connect failed';
|
||||
export const CONNECTION_LOG_MSG_SEND_FAILED = 'send failed';
|
||||
export const CONNECTION_LOG_MSG_FRAME_DECODE_FAILED = 'frame decode failed';
|
||||
export const CONNECTION_LOG_MSG_FRAME_ENCODE_FAILED = 'frame encode failed';
|
||||
export const CONNECTION_LOG_MSG_AUTH_FAILED = 'auth failed';
|
||||
export const CONNECTION_LOG_MSG_REAUTH_FAILED = 'reauthentication failed';
|
||||
export const CONNECTION_LOG_MSG_HEARTBEAT_TIMEOUT = 'heartbeat timeout';
|
||||
export const CONNECTION_LOG_MSG_SESSION_CHANGED = 'session changed; reauthenticating connection';
|
||||
export const CONNECTION_LOG_MSG_SESSION_REVOKED = 'session revoked; disconnecting connection';
|
||||
export const CONNECTION_LOG_MSG_SESSION_EXPIRED = 'session expired; disconnecting connection';
|
||||
export const CONNECTION_LOG_MSG_SESSION_REFRESHED = 'session refreshed; reauthenticating connection';
|
||||
export const CONNECTION_LOG_MSG_BROWSER_RECONNECT = 'browser lifecycle reconnect requested';
|
||||
|
||||
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: ';
|
||||
export const CONNECTION_ERROR_MSG_NOT_FOUND_PREFIX = 'connection not found: ';
|
||||
export const CONNECTION_ERROR_MSG_INVALID_NAME_PREFIX = 'invalid connection name: ';
|
||||
export const CONNECTION_ERROR_MSG_INVALID_FRAME = 'invalid connection frame';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_MUST_BE_OBJECT = 'Connection frame must be an object';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_TYPE_MUST_BE_STRING =
|
||||
'Connection frame type must be a non-empty string';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_ID_MUST_BE_STRING = 'Connection frame id must be a string';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_TOPIC_MUST_BE_STRING = 'Connection frame topic must be a string';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_REPLY_TO_MUST_BE_STRING = 'Connection frame replyTo must be a string';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_ACK_MUST_BE_BOOLEAN = 'Connection frame ack must be a boolean';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_TS_MUST_BE_NUMBER = 'Connection frame ts must be a number';
|
||||
export const CONNECTION_ERROR_MSG_FRAME_ERROR_MUST_BE_OBJECT = 'Connection frame error must be an object';
|
||||
export const CONNECTION_ERROR_MSG_JSON_DECODE_FAILED = 'Connection JSON payload is invalid';
|
||||
export const CONNECTION_ERROR_MSG_WEBSOCKET_UNAVAILABLE = 'WebSocket is not available in this runtime';
|
||||
export const CONNECTION_ERROR_MSG_CHANNEL_ALREADY_EXISTS_PREFIX = 'channel already exists: ';
|
||||
export const CONNECTION_ERROR_MSG_CHANNEL_NOT_FOUND_PREFIX = 'channel not found: ';
|
||||
@ -0,0 +1,141 @@
|
||||
import {
|
||||
LogLevel,
|
||||
createCatalogDiagnostics,
|
||||
type DiagnosticCatalog,
|
||||
type DiagnosticEvent,
|
||||
type Diagnostics
|
||||
} from '$libs/logger';
|
||||
import {
|
||||
CONNECTION_DIAGNOSTIC_EVENTS,
|
||||
CONNECTION_EVENT_MESSAGE,
|
||||
CONNECTION_MODULE,
|
||||
CONNECTION_LOG_MSG_AUTH_FAILED,
|
||||
CONNECTION_LOG_MSG_BROWSER_RECONNECT,
|
||||
CONNECTION_LOG_MSG_CONNECT_FAILED,
|
||||
CONNECTION_LOG_MSG_FRAME_DECODE_FAILED,
|
||||
CONNECTION_LOG_MSG_FRAME_ENCODE_FAILED,
|
||||
CONNECTION_LOG_MSG_HEARTBEAT_TIMEOUT,
|
||||
CONNECTION_LOG_MSG_REAUTH_FAILED,
|
||||
CONNECTION_LOG_MSG_RECONNECT_EXHAUSTED,
|
||||
CONNECTION_LOG_MSG_SEND_FAILED,
|
||||
CONNECTION_LOG_MSG_SESSION_CHANGED,
|
||||
CONNECTION_LOG_MSG_SESSION_EXPIRED,
|
||||
CONNECTION_LOG_MSG_SESSION_REFRESHED,
|
||||
CONNECTION_LOG_MSG_SESSION_REVOKED,
|
||||
CONNECTION_LOG_MSG_TRANSPORT_ERROR
|
||||
} from './consts.ts';
|
||||
import { listenerThrewMessage } from './helpers.ts';
|
||||
import type { Logger } from '$libs/logger';
|
||||
|
||||
export type ConnectionDiagnosticType =
|
||||
(typeof CONNECTION_DIAGNOSTIC_EVENTS)[keyof typeof CONNECTION_DIAGNOSTIC_EVENTS];
|
||||
|
||||
export type ConnectionDiagnosticEvent = DiagnosticEvent<ConnectionDiagnosticType>;
|
||||
export type ConnectionDiagnostics = Diagnostics<ConnectionDiagnosticEvent>;
|
||||
|
||||
const CONNECTION_DIAGNOSTIC_LOGS: DiagnosticCatalog<ConnectionDiagnosticEvent> = {
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.AUTH_FAILED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONNECTION_LOG_MSG_AUTH_FAILED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.BROWSER_RECONNECT]: {
|
||||
level: LogLevel.DEBUG,
|
||||
message: CONNECTION_LOG_MSG_BROWSER_RECONNECT
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.CONNECT_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONNECTION_LOG_MSG_CONNECT_FAILED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.FRAME_DECODE_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONNECTION_LOG_MSG_FRAME_DECODE_FAILED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.FRAME_ENCODE_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONNECTION_LOG_MSG_FRAME_ENCODE_FAILED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.HEARTBEAT_TIMEOUT]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONNECTION_LOG_MSG_HEARTBEAT_TIMEOUT
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.LISTENER_THREW]: (event) => ({
|
||||
level: LogLevel.ERROR,
|
||||
message: listenerThrewMessage(listenerEventName(event.meta))
|
||||
}),
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.REAUTH_FAILED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONNECTION_LOG_MSG_REAUTH_FAILED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.RECONNECT_EXHAUSTED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONNECTION_LOG_MSG_RECONNECT_EXHAUSTED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.SEND_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONNECTION_LOG_MSG_SEND_FAILED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.SESSION_CHANGED]: {
|
||||
level: LogLevel.DEBUG,
|
||||
message: CONNECTION_LOG_MSG_SESSION_CHANGED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.SESSION_EXPIRED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONNECTION_LOG_MSG_SESSION_EXPIRED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.SESSION_REFRESHED]: {
|
||||
level: LogLevel.DEBUG,
|
||||
message: CONNECTION_LOG_MSG_SESSION_REFRESHED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.SESSION_REVOKED]: {
|
||||
level: LogLevel.WARN,
|
||||
message: CONNECTION_LOG_MSG_SESSION_REVOKED
|
||||
},
|
||||
[CONNECTION_DIAGNOSTIC_EVENTS.TRANSPORT_ERROR]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: CONNECTION_LOG_MSG_TRANSPORT_ERROR
|
||||
}
|
||||
};
|
||||
|
||||
function listenerEventName(meta: unknown): string {
|
||||
if (typeof meta !== 'object' || meta === null) return CONNECTION_EVENT_MESSAGE;
|
||||
if (!('event' in meta)) return CONNECTION_EVENT_MESSAGE;
|
||||
const event = meta.event;
|
||||
return typeof event === 'string' && event.length > 0 ? event : CONNECTION_EVENT_MESSAGE;
|
||||
}
|
||||
|
||||
export function createConnectionDiagnostics(input: {
|
||||
readonly logger?: Logger;
|
||||
readonly scope: string;
|
||||
}): ConnectionDiagnostics {
|
||||
return createCatalogDiagnostics({
|
||||
logger: input.logger,
|
||||
defaultCategory: input.scope,
|
||||
catalog: CONNECTION_DIAGNOSTIC_LOGS
|
||||
});
|
||||
}
|
||||
|
||||
export function emitConnectionDiagnostic(
|
||||
diagnostics: ConnectionDiagnostics,
|
||||
type: ConnectionDiagnosticType,
|
||||
meta?: unknown
|
||||
): void {
|
||||
diagnostics.emit({
|
||||
artifact: CONNECTION_MODULE,
|
||||
type,
|
||||
meta
|
||||
});
|
||||
}
|
||||
|
||||
export function emitScopedConnectionDiagnostic(
|
||||
diagnostics: ConnectionDiagnostics,
|
||||
scope: string,
|
||||
type: ConnectionDiagnosticType,
|
||||
meta?: unknown
|
||||
): void {
|
||||
diagnostics.emit({
|
||||
artifact: CONNECTION_MODULE,
|
||||
scope,
|
||||
type,
|
||||
meta
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
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';
|
||||
|
||||
export class ConnectionDisposedError extends CodeError {
|
||||
constructor(message: string, options?: { cause?: unknown }) {
|
||||
super(CONNECTION_ERR_DISPOSED, { message, cause: options?.cause });
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionAlreadyExistsError extends CodeError {
|
||||
readonly connection: string;
|
||||
constructor(message: string, connection: string) {
|
||||
super(CONNECTION_ERR_CONNECTION_ALREADY_EXISTS, { message });
|
||||
this.connection = connection;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionNotFoundError extends CodeError {
|
||||
readonly connection: string;
|
||||
constructor(message: string, connection: string) {
|
||||
super(CONNECTION_ERR_CONNECTION_NOT_FOUND, { message });
|
||||
this.connection = connection;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionInvalidNameError extends CodeError {
|
||||
constructor(message: string, options?: { cause?: unknown }) {
|
||||
super(CONNECTION_ERR_INVALID_CONNECTION_NAME, { message, cause: options?.cause });
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionInvalidFrameError extends CodeError {
|
||||
constructor(message: string, options?: { cause?: unknown }) {
|
||||
super(CONNECTION_ERR_INVALID_FRAME, { message, cause: options?.cause });
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionChannelAlreadyExistsError extends CodeError {
|
||||
readonly channel: string;
|
||||
constructor(message: string, channel: string) {
|
||||
super(CONNECTION_ERR_CHANNEL_ALREADY_EXISTS, { message });
|
||||
this.channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionChannelNotFoundError extends CodeError {
|
||||
readonly channel: string;
|
||||
constructor(message: string, channel: string) {
|
||||
super(CONNECTION_ERR_CHANNEL_NOT_FOUND, { message });
|
||||
this.channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionWebSocketUnavailableError extends CodeError {
|
||||
constructor(message: string, options?: { cause?: unknown }) {
|
||||
super(CONNECTION_ERR_WEBSOCKET_UNAVAILABLE, { message, cause: options?.cause });
|
||||
}
|
||||
}
|
||||
|
||||
export function isConnectionDisposedError(value: unknown): value is ConnectionDisposedError {
|
||||
return value instanceof ConnectionDisposedError;
|
||||
}
|
||||
|
||||
export function isConnectionAlreadyExistsError(
|
||||
value: unknown
|
||||
): value is ConnectionAlreadyExistsError {
|
||||
return value instanceof ConnectionAlreadyExistsError;
|
||||
}
|
||||
|
||||
export function isConnectionNotFoundError(
|
||||
value: unknown
|
||||
): value is ConnectionNotFoundError {
|
||||
return value instanceof ConnectionNotFoundError;
|
||||
}
|
||||
|
||||
export function isConnectionInvalidNameError(
|
||||
value: unknown
|
||||
): value is ConnectionInvalidNameError {
|
||||
return value instanceof ConnectionInvalidNameError;
|
||||
}
|
||||
|
||||
export function isConnectionInvalidFrameError(value: unknown): value is ConnectionInvalidFrameError {
|
||||
return value instanceof ConnectionInvalidFrameError;
|
||||
}
|
||||
|
||||
export function isConnectionChannelAlreadyExistsError(
|
||||
value: unknown
|
||||
): value is ConnectionChannelAlreadyExistsError {
|
||||
return value instanceof ConnectionChannelAlreadyExistsError;
|
||||
}
|
||||
|
||||
export function isConnectionChannelNotFoundError(
|
||||
value: unknown
|
||||
): value is ConnectionChannelNotFoundError {
|
||||
return value instanceof ConnectionChannelNotFoundError;
|
||||
}
|
||||
|
||||
export function isConnectionWebSocketUnavailableError(
|
||||
value: unknown
|
||||
): value is ConnectionWebSocketUnavailableError {
|
||||
return value instanceof ConnectionWebSocketUnavailableError;
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import {
|
||||
CONNECTION_ERROR_MSG_ALREADY_EXISTS_PREFIX,
|
||||
CONNECTION_ERROR_MSG_CHANNEL_ALREADY_EXISTS_PREFIX,
|
||||
CONNECTION_ERROR_MSG_CHANNEL_NOT_FOUND_PREFIX,
|
||||
CONNECTION_ERROR_MSG_DISPOSED_SUFFIX,
|
||||
CONNECTION_ERROR_MSG_INVALID_NAME_PREFIX,
|
||||
CONNECTION_ERROR_MSG_NOT_FOUND_PREFIX,
|
||||
CONNECTION_ERROR_PREFIX,
|
||||
CONNECTION_ID_PREFIX,
|
||||
CONNECTION_ID_SEPARATOR,
|
||||
CONNECTION_MODULE,
|
||||
CONNECTION_LOGGER_SCOPE_SEPARATOR,
|
||||
CONNECTION_LOG_MSG_LISTENER_THREW_PREFIX
|
||||
} from './consts.ts';
|
||||
|
||||
export function disposedErrorMessage(method: string): string {
|
||||
return `${CONNECTION_ERROR_PREFIX}${method}${CONNECTION_ERROR_MSG_DISPOSED_SUFFIX}`;
|
||||
}
|
||||
|
||||
export function alreadyExistsErrorMessage(name: string): string {
|
||||
return `${CONNECTION_ERROR_PREFIX}${CONNECTION_ERROR_MSG_ALREADY_EXISTS_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function notFoundErrorMessage(name: string): string {
|
||||
return `${CONNECTION_ERROR_PREFIX}${CONNECTION_ERROR_MSG_NOT_FOUND_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function invalidNameErrorMessage(name: unknown): string {
|
||||
return `${CONNECTION_ERROR_PREFIX}${CONNECTION_ERROR_MSG_INVALID_NAME_PREFIX}${String(name)}`;
|
||||
}
|
||||
|
||||
export function channelAlreadyExistsErrorMessage(name: string): string {
|
||||
return `${CONNECTION_ERROR_PREFIX}${CONNECTION_ERROR_MSG_CHANNEL_ALREADY_EXISTS_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function channelNotFoundErrorMessage(name: string): string {
|
||||
return `${CONNECTION_ERROR_PREFIX}${CONNECTION_ERROR_MSG_CHANNEL_NOT_FOUND_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
export function listenerThrewMessage(event: string): string {
|
||||
return `${CONNECTION_LOG_MSG_LISTENER_THREW_PREFIX}${event}`;
|
||||
}
|
||||
|
||||
export function loggerScope(name: string, topic?: string): string {
|
||||
return topic === undefined
|
||||
? `${CONNECTION_MODULE}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${name}`
|
||||
: `${CONNECTION_MODULE}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${name}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${topic}`;
|
||||
}
|
||||
|
||||
export function timerKey(connection: string, kind: string, id?: string): string {
|
||||
return id === undefined
|
||||
? `${CONNECTION_MODULE}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${connection}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${kind}`
|
||||
: `${CONNECTION_MODULE}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${connection}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${kind}${CONNECTION_LOGGER_SCOPE_SEPARATOR}${id}`;
|
||||
}
|
||||
|
||||
export function createConnectionIdFactory(connectionName: string): () => string {
|
||||
let next = 0;
|
||||
return () => {
|
||||
next += 1;
|
||||
return [CONNECTION_ID_PREFIX, connectionName, String(next)].join(CONNECTION_ID_SEPARATOR);
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
import {
|
||||
CONNECTION_FRAME_KEY_ACK,
|
||||
CONNECTION_FRAME_KEY_ERROR,
|
||||
CONNECTION_FRAME_KEY_ID,
|
||||
CONNECTION_FRAME_KEY_PAYLOAD,
|
||||
CONNECTION_FRAME_KEY_REPLY_TO,
|
||||
CONNECTION_FRAME_KEY_TOPIC,
|
||||
CONNECTION_FRAME_KEY_TS,
|
||||
CONNECTION_FRAME_KEY_TYPE,
|
||||
CONNECTION_ERROR_MSG_FRAME_ACK_MUST_BE_BOOLEAN,
|
||||
CONNECTION_ERROR_MSG_FRAME_ERROR_MUST_BE_OBJECT,
|
||||
CONNECTION_ERROR_MSG_FRAME_ID_MUST_BE_STRING,
|
||||
CONNECTION_ERROR_MSG_FRAME_MUST_BE_OBJECT,
|
||||
CONNECTION_ERROR_MSG_FRAME_REPLY_TO_MUST_BE_STRING,
|
||||
CONNECTION_ERROR_MSG_FRAME_TOPIC_MUST_BE_STRING,
|
||||
CONNECTION_ERROR_MSG_FRAME_TS_MUST_BE_NUMBER,
|
||||
CONNECTION_ERROR_MSG_FRAME_TYPE_MUST_BE_STRING
|
||||
} from './consts.ts';
|
||||
import { ConnectionInvalidFrameError } from './errors.ts';
|
||||
import type { ConnectionFrame } from './types.ts';
|
||||
|
||||
export function assertConnectionFrame(value: unknown): asserts value is ConnectionFrame {
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_MUST_BE_OBJECT);
|
||||
}
|
||||
const frame = value as Record<string, unknown>;
|
||||
if (typeof frame[CONNECTION_FRAME_KEY_TYPE] !== 'string' || frame[CONNECTION_FRAME_KEY_TYPE].length === 0) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_TYPE_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONNECTION_FRAME_KEY_ID in frame &&
|
||||
frame[CONNECTION_FRAME_KEY_ID] !== undefined &&
|
||||
typeof frame[CONNECTION_FRAME_KEY_ID] !== 'string'
|
||||
) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_ID_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONNECTION_FRAME_KEY_TOPIC in frame &&
|
||||
frame[CONNECTION_FRAME_KEY_TOPIC] !== undefined &&
|
||||
typeof frame[CONNECTION_FRAME_KEY_TOPIC] !== 'string'
|
||||
) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_TOPIC_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONNECTION_FRAME_KEY_REPLY_TO in frame &&
|
||||
frame[CONNECTION_FRAME_KEY_REPLY_TO] !== undefined &&
|
||||
typeof frame[CONNECTION_FRAME_KEY_REPLY_TO] !== 'string'
|
||||
) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_REPLY_TO_MUST_BE_STRING);
|
||||
}
|
||||
if (
|
||||
CONNECTION_FRAME_KEY_ACK in frame &&
|
||||
frame[CONNECTION_FRAME_KEY_ACK] !== undefined &&
|
||||
typeof frame[CONNECTION_FRAME_KEY_ACK] !== 'boolean'
|
||||
) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_ACK_MUST_BE_BOOLEAN);
|
||||
}
|
||||
if (
|
||||
CONNECTION_FRAME_KEY_TS in frame &&
|
||||
frame[CONNECTION_FRAME_KEY_TS] !== undefined &&
|
||||
typeof frame[CONNECTION_FRAME_KEY_TS] !== 'number'
|
||||
) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_TS_MUST_BE_NUMBER);
|
||||
}
|
||||
if (
|
||||
CONNECTION_FRAME_KEY_ERROR in frame &&
|
||||
frame[CONNECTION_FRAME_KEY_ERROR] !== undefined &&
|
||||
(frame[CONNECTION_FRAME_KEY_ERROR] === null || typeof frame[CONNECTION_FRAME_KEY_ERROR] !== 'object')
|
||||
) {
|
||||
throw new ConnectionInvalidFrameError(CONNECTION_ERROR_MSG_FRAME_ERROR_MUST_BE_OBJECT);
|
||||
}
|
||||
}
|
||||
|
||||
export function createFrame(input: ConnectionFrame): ConnectionFrame {
|
||||
assertConnectionFrame(input);
|
||||
const frame: Record<string, unknown> = {
|
||||
[CONNECTION_FRAME_KEY_TYPE]: input.type,
|
||||
[CONNECTION_FRAME_KEY_PAYLOAD]: input.payload
|
||||
};
|
||||
if (input.id !== undefined) frame[CONNECTION_FRAME_KEY_ID] = input.id;
|
||||
if (input.topic !== undefined) frame[CONNECTION_FRAME_KEY_TOPIC] = input.topic;
|
||||
if (input.ts !== undefined) frame[CONNECTION_FRAME_KEY_TS] = input.ts;
|
||||
if (input.ack !== undefined) frame[CONNECTION_FRAME_KEY_ACK] = input.ack;
|
||||
if (input.replyTo !== undefined) frame[CONNECTION_FRAME_KEY_REPLY_TO] = input.replyTo;
|
||||
if (input.error !== undefined) frame[CONNECTION_FRAME_KEY_ERROR] = input.error;
|
||||
return frame as unknown as ConnectionFrame;
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export const FMTS_MODULE = 'fmts';
|
||||
export const FMTS_DEFAULT_LOCALE = 'en-US';
|
||||
export const FMTS_AUTO_VALUE = 'auto';
|
||||
@ -1 +0,0 @@
|
||||
export const FMTS_DATES_MODULE = 'fmts.dates';
|
||||
@ -1,5 +0,0 @@
|
||||
import { FMTS_MODULE } from './consts.ts';
|
||||
|
||||
export const FORMATS_ERRORS = {
|
||||
INVALID_LOCALE: (locale: string): string => `[${FMTS_MODULE}] Invalid locale: "${locale}".`
|
||||
} as const;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue