Layer-boundary cleanup (audit-1-5.md section 1): - fend → adom: move apply.ts to libs/dom; FrontendDom is now DomApplier; fend falls back to bare applyChange instead of constructing ActiveDom - sium → lang: full split — pure code (consts, types, guards, helpers, errors, json, plural, plural_rules, diagnostics) moves to libs/lang; arts/lang keeps engine + active wrappers and re-exports for back-compat - conn → timr: minimum split — types and public constants move to libs/timers; conn imports types from $libs/timers; EngineConnectionsOptions.timers is now required (no more silent createEngineTimers fallback). Tests updated to construct shared timers per beforeEach Bus contract finishing touches (continued from prior session): - subscribe() returns the unsubscribe function directly - publishCausedBy() propagates correlationId/causationId - invokeListener hook + Svelte adapter wraps listeners in untrack - maxReentrancyDepth guard with BusReentrancyLimitError (fatal — bypasses listener-error trap) - DEV-mode structuredClone payload check raising BusInvalidPayloadError - BusListenerFailure carries envelopeId/correlationId/causationId - createBusRecent active wrapper, bus-context.svelte.ts, APP_EVENT_RUNTIMES table with assertEventCanFire wired into every publishApp* helper - App switches to createSvelteEngineBus audit-1-5.md captures the full ecosystem audit (5 axes); section 1 is now closed by these changes. Sections 2-5 remain open. Verification: svelte-check 1395/0 errors; server 1230 tests passed; client 19 tests passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>master
parent
36868d3efe
commit
bf3891ca91
@ -1,37 +1,92 @@
|
||||
import {
|
||||
BUS_DIAGNOSTIC_EVENTS,
|
||||
BUS_LOG_CATEGORY,
|
||||
BUS_LOG_MSG_LISTENER_FAILED,
|
||||
BUS_LOG_MSG_LISTENER_LEAK_WARNING
|
||||
} from './consts.ts';
|
||||
import type { BusEnvelope, BusListenerFailure } from './types.ts';
|
||||
import type { Logger } from '$libs/logr';
|
||||
import {
|
||||
LogLevel,
|
||||
createCatalogDiagnostics,
|
||||
type DiagnosticCatalog,
|
||||
type DiagnosticEvent,
|
||||
type Diagnostics,
|
||||
type Logger
|
||||
} from '$libs/logr';
|
||||
|
||||
export type BusDiagnosticType =
|
||||
| typeof BUS_DIAGNOSTIC_EVENTS.LISTENER_FAILED
|
||||
| typeof BUS_DIAGNOSTIC_EVENTS.LISTENER_LEAK_WARNING;
|
||||
|
||||
export type BusDiagnosticMeta =
|
||||
| {
|
||||
readonly envelope: BusEnvelope;
|
||||
readonly listenerId?: string;
|
||||
readonly eventType: string;
|
||||
readonly error: unknown;
|
||||
}
|
||||
| {
|
||||
readonly type: string;
|
||||
readonly count: number;
|
||||
readonly max: number;
|
||||
};
|
||||
|
||||
export type BusDiagnosticEvent = DiagnosticEvent<BusDiagnosticType, BusDiagnosticMeta>;
|
||||
export type BusDiagnostics = Diagnostics<BusDiagnosticEvent>;
|
||||
|
||||
const BUS_DIAGNOSTIC_LOGS: DiagnosticCatalog<BusDiagnosticEvent> = {
|
||||
[BUS_DIAGNOSTIC_EVENTS.LISTENER_FAILED]: {
|
||||
level: LogLevel.ERROR,
|
||||
message: BUS_LOG_MSG_LISTENER_FAILED
|
||||
},
|
||||
[BUS_DIAGNOSTIC_EVENTS.LISTENER_LEAK_WARNING]: {
|
||||
level: LogLevel.WARN,
|
||||
message: BUS_LOG_MSG_LISTENER_LEAK_WARNING
|
||||
}
|
||||
};
|
||||
|
||||
export function createBusDiagnostics(logger?: Logger): BusDiagnostics {
|
||||
return createCatalogDiagnostics({
|
||||
logger,
|
||||
defaultCategory: BUS_LOG_CATEGORY,
|
||||
catalog: BUS_DIAGNOSTIC_LOGS
|
||||
});
|
||||
}
|
||||
|
||||
export function emitBusDiagnostic(
|
||||
diagnostics: BusDiagnostics,
|
||||
type: BusDiagnosticType,
|
||||
meta: BusDiagnosticMeta
|
||||
): void {
|
||||
diagnostics.emit({
|
||||
artifact: BUS_LOG_CATEGORY,
|
||||
type,
|
||||
meta
|
||||
});
|
||||
}
|
||||
|
||||
export function logBusListenerFailure(input: {
|
||||
readonly logger: Logger;
|
||||
export function emitBusListenerFailureDiagnostic(input: {
|
||||
readonly diagnostics: BusDiagnostics;
|
||||
readonly envelope: BusEnvelope;
|
||||
readonly failure: BusListenerFailure;
|
||||
}): void {
|
||||
input.logger.error(BUS_LOG_CATEGORY, BUS_LOG_MSG_LISTENER_FAILED, {
|
||||
error: input.failure.error,
|
||||
context: {
|
||||
envelope: input.envelope,
|
||||
listenerId: input.failure.listenerId,
|
||||
eventType: input.failure.type
|
||||
}
|
||||
emitBusDiagnostic(input.diagnostics, BUS_DIAGNOSTIC_EVENTS.LISTENER_FAILED, {
|
||||
envelope: input.envelope,
|
||||
...(input.failure.listenerId !== undefined ? { listenerId: input.failure.listenerId } : {}),
|
||||
eventType: input.failure.type,
|
||||
error: input.failure.error
|
||||
});
|
||||
}
|
||||
|
||||
export function logBusListenerLeakWarning(input: {
|
||||
readonly logger: Logger;
|
||||
export function emitBusListenerLeakWarningDiagnostic(input: {
|
||||
readonly diagnostics: BusDiagnostics;
|
||||
readonly type: string;
|
||||
readonly count: number;
|
||||
readonly max: number;
|
||||
}): void {
|
||||
input.logger.warn(BUS_LOG_CATEGORY, BUS_LOG_MSG_LISTENER_LEAK_WARNING, {
|
||||
context: {
|
||||
type: input.type,
|
||||
count: input.count,
|
||||
max: input.max
|
||||
}
|
||||
emitBusDiagnostic(input.diagnostics, BUS_DIAGNOSTIC_EVENTS.LISTENER_LEAK_WARNING, {
|
||||
type: input.type,
|
||||
count: input.count,
|
||||
max: input.max
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* `libs/lang` — pure contracts and helpers for the lang artifact.
|
||||
*
|
||||
* Mirrors the `libs/buss` / `libs/logr` / `libs/dom` pattern: this module
|
||||
* exports interfaces, generic constants, error catalogs, pure helpers,
|
||||
* plural rules, JSON serialization, and diagnostics builders. It does
|
||||
* NOT include the engine implementation; that lives in
|
||||
* `arts/lang/engine-lang.ts` and the active wrappers in
|
||||
* `arts/lang/active-lang.svelte.ts` / `arts/lang/mono-lang.svelte.ts`.
|
||||
*
|
||||
* Modules that need types or pure helpers from lang import from
|
||||
* `$libs/lang`. Only the composition root (`aapp`) and tests touch
|
||||
* `$lang` to reach the engine factories.
|
||||
*/
|
||||
|
||||
export {
|
||||
ID_FALLBACK_SEPARATOR,
|
||||
ID_PREFIX,
|
||||
LANG_DIAGNOSTIC_EVENTS,
|
||||
LOGGER_CATEGORY,
|
||||
MAX_RESOLVE_DEPTH,
|
||||
MONO_LANG_CATEGORY
|
||||
} from './consts.ts';
|
||||
|
||||
export { createLangDiagnostics, emitLangDiagnostic } from './diagnostics.ts';
|
||||
export type {
|
||||
LangDiagnosticEvent,
|
||||
LangDiagnosticMeta,
|
||||
LangDiagnostics,
|
||||
LangDiagnosticType
|
||||
} from './diagnostics.ts';
|
||||
|
||||
export { LANG_ERRORS } from './errors.ts';
|
||||
|
||||
export { isLangRef, isLangRecord, isLangString } from './guards.ts';
|
||||
|
||||
export {
|
||||
asLangString,
|
||||
deepMerge,
|
||||
interpolateTemplate,
|
||||
isLangBranch,
|
||||
makeLangRecord,
|
||||
parseLangRef,
|
||||
parsePathFallback,
|
||||
resolveLocaleInRecord,
|
||||
resolvePath,
|
||||
resolveRecordFallback
|
||||
} from './helpers.ts';
|
||||
export type { RecordLookup } from './helpers.ts';
|
||||
|
||||
export { JSONToLangNode, langNodeToJSON } from './json.ts';
|
||||
export type { LangJSONObject, LangJSONValue } from './json.ts';
|
||||
|
||||
export { getPluralConfig, p } from './plural.ts';
|
||||
|
||||
export { pluralRule } from './plural_rules.ts';
|
||||
export type { PluralCategory } from './plural_rules.ts';
|
||||
|
||||
export type {
|
||||
ActiveLang,
|
||||
EngineLang,
|
||||
GetTypeAtPath,
|
||||
HasParams,
|
||||
LangBase,
|
||||
LangBranch,
|
||||
LangFn,
|
||||
LangNode,
|
||||
LangParams,
|
||||
LangPluralFn,
|
||||
LangRecord,
|
||||
LangRef,
|
||||
LangString,
|
||||
LangValue,
|
||||
LeafPaths,
|
||||
ParamsFor,
|
||||
PluralConfig,
|
||||
PluralForms,
|
||||
SupportedLocale
|
||||
} from './types.ts';
|
||||
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Public constants of the timers contract.
|
||||
*
|
||||
* Mirrors the `libs/buss` / `libs/lang` pattern: the discrete values
|
||||
* exposed in public types live here so consumers can import them
|
||||
* without crossing into `arts/timr`. Engine-internal constants (error
|
||||
* messages, method names, log message prefixes) stay in
|
||||
* `arts/timr/consts.ts`.
|
||||
*/
|
||||
|
||||
/** Logger category emitted by the engine. */
|
||||
export const LOGGER_CATEGORY = 'timr';
|
||||
|
||||
export const TIMER_DIAGNOSTIC_EVENTS = {
|
||||
LISTENER_THREW: 'listener_threw',
|
||||
SCHEDULE_AT_PAST: 'schedule_at_past',
|
||||
TASK_FAILED: 'task_failed'
|
||||
} as const;
|
||||
|
||||
// ── Timer status ───────────────────────────────────────────────────────────
|
||||
//
|
||||
// Discrete values of `TimerStatus`. The engine moves an entry through
|
||||
// `pending → running → (completed | failed)` for one-shots, and back to
|
||||
// `pending` for intervals between ticks. `cancelled` is terminal.
|
||||
|
||||
export const TIMER_STATUS_PENDING = 'pending';
|
||||
export const TIMER_STATUS_RUNNING = 'running';
|
||||
export const TIMER_STATUS_CANCELLED = 'cancelled';
|
||||
export const TIMER_STATUS_COMPLETED = 'completed';
|
||||
export const TIMER_STATUS_FAILED = 'failed';
|
||||
|
||||
export const TIMER_STATUSES = [
|
||||
TIMER_STATUS_PENDING,
|
||||
TIMER_STATUS_RUNNING,
|
||||
TIMER_STATUS_CANCELLED,
|
||||
TIMER_STATUS_COMPLETED,
|
||||
TIMER_STATUS_FAILED
|
||||
] as const;
|
||||
|
||||
// ── Timer kind ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const TIMER_KIND_TIMEOUT = 'timeout';
|
||||
export const TIMER_KIND_INTERVAL = 'interval';
|
||||
|
||||
export const TIMER_KINDS = [TIMER_KIND_TIMEOUT, TIMER_KIND_INTERVAL] as const;
|
||||
|
||||
// ── Event types ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Events emitted via `EngineTimers.onChange`. Every state transition
|
||||
// surfaces as exactly one of these. The Active wrapper subscribes to
|
||||
// the same channel external listeners use — no parallel notification
|
||||
// path.
|
||||
//
|
||||
// Names are scoped with the `timr.` prefix for log/devtool
|
||||
// disambiguation against cache, connection or any other emitter.
|
||||
|
||||
export const TIMER_EVENT_SCHEDULED = 'timr.scheduled';
|
||||
export const TIMER_EVENT_RUNNING = 'timr.running';
|
||||
export const TIMER_EVENT_COMPLETED = 'timr.completed';
|
||||
export const TIMER_EVENT_FAILED = 'timr.failed';
|
||||
export const TIMER_EVENT_CANCELLED = 'timr.cancelled';
|
||||
export const TIMER_EVENT_DISPOSED = 'timr.disposed';
|
||||
|
||||
// ── Scope helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
export const DEFAULT_TIMER_SCOPE_SEPARATOR = ':';
|
||||
@ -1,2 +1,4 @@
|
||||
export * from './debounce.ts';
|
||||
export * from './backoff.ts';
|
||||
export * from './consts.ts';
|
||||
export * from './debounce.ts';
|
||||
export * from './types.ts';
|
||||
|
||||
Loading…
Reference in new issue