Spaces:
Runtime error
Runtime error
Aryan Mishra
feat: initial commit - Multilingual ABSA project setup with 6 phases, 36 requirements
a6b96c2 | ; | |
| /** | |
| * Command Routing Hub β issue #3788, simplified in #175, typed in #176, observability in #177. | |
| * | |
| * A pure-result dispatch hub that centralizes CJS routing, | |
| * the error taxonomy, and the no-throw contract that all command-family routers | |
| * currently duplicate independently. | |
| * | |
| * Design: | |
| * createHub({ cjsRegistry, manifest }) -> hub | |
| * hub.dispatch({ family, subcommand, args, cwd, raw }) -> Result | |
| * | |
| * Result = { ok: true, data } | |
| * | { ok: false, kind: 'UnknownCommand', command: string } | |
| * | { ok: false, kind: 'InvalidArgs', arg: string, reason: string } | |
| * | { ok: false, kind: 'HandlerRefusal', reason: string } | |
| * | { ok: false, kind: 'HandlerFailure', message: string, cause?: Error } | |
| * | |
| * Invariants: | |
| * - Hub always routes through CJS handlers. There is no SDK path (#175). | |
| * - Hub never prints to stdout/stderr, never calls process.exit. | |
| * - Hub never throws β all internal throws are caught and converted to | |
| * { ok: false, kind: 'HandlerFailure', message, cause }. | |
| * - The kind taxonomy is closed. Callers switch on ERROR_KINDS values. | |
| * - Each error variant carries ONLY its own typed payload (#176). | |
| * No cross-variant `message`/`details` escape hatches. | |
| * | |
| * ADR-457 build-at-publish: the hand-written bin/lib/command-routing-hub.cjs collapsed | |
| * to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour from | |
| * the prior hand-written .cjs; only types are added. | |
| */ | |
| const event_cjs_1 = require("./observability/event.cjs"); | |
| // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| const observabilityLogger = require("./observability/logger.cjs"); | |
| const { createNoOpLogger } = observabilityLogger; | |
| // βββ Error kind constants βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Closed error-kind enum. Export as a frozen object so callers can switch on | |
| * ERROR_KINDS.UnknownCommand etc. without relying on bare string literals. | |
| * | |
| * #175: SdkLoadFailed and SdkDispatchFailed removed β Hub is CJS-only. | |
| * #176: Field renamed errorKind β kind; payloads are typed per variant. | |
| * | |
| * @readonly | |
| */ | |
| const ERROR_KINDS = Object.freeze({ | |
| /** The requested family/subcommand combination is not present in the manifest. */ | |
| UnknownCommand: 'UnknownCommand', | |
| /** The handler rejected the supplied arguments before executing. */ | |
| InvalidArgs: 'InvalidArgs', | |
| /** A CJS handler returned an explicit refusal (e.g. unsupported subcommand). */ | |
| HandlerRefusal: 'HandlerRefusal', | |
| /** A handler threw an unexpected exception. */ | |
| HandlerFailure: 'HandlerFailure', | |
| }); | |
| // βββ Internal helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Safe JSON serialisation that never throws. | |
| */ | |
| function _safeJson(value) { | |
| try { | |
| return JSON.stringify(value); | |
| } | |
| catch { | |
| return String(value); | |
| } | |
| } | |
| // βββ Typed-payload factories (#176) ββββββββββββββββββββββββββββββββββββββββββ | |
| // Each factory returns a frozen discriminated-union variant for its kind. | |
| // No cross-variant fields bleed between variants. | |
| // Finding 3: all factory returns are Object.freeze'd so callers cannot mutate | |
| // the variant invariant. | |
| function makeUnknownCommand(command) { | |
| return Object.freeze({ ok: false, kind: ERROR_KINDS.UnknownCommand, command }); | |
| } | |
| function makeInvalidArgs(arg, reason) { | |
| return Object.freeze({ ok: false, kind: ERROR_KINDS.InvalidArgs, arg, reason }); | |
| } | |
| function makeHandlerRefusal(reason) { | |
| return Object.freeze({ ok: false, kind: ERROR_KINDS.HandlerRefusal, reason }); | |
| } | |
| /** | |
| * @param message - Human-readable description of the failure. | |
| * @param cause - The original thrown Error, when available. | |
| * Non-Error values (strings, plain objects, etc.) are wrapped in an Error | |
| * with `.thrown` set to the original value. null/undefined β no cause field. | |
| */ | |
| function makeHandlerFailure(message, cause) { | |
| const obj = { ok: false, kind: ERROR_KINDS.HandlerFailure, message }; | |
| if (cause != null) { | |
| if (cause instanceof Error) { | |
| obj.cause = cause; | |
| } | |
| else { | |
| // Finding 4: wrap non-Error cause so downstream .cause.stack never silently returns undefined | |
| const wrapper = new Error('non-Error cause: ' + _safeJson(cause)); | |
| wrapper.thrown = cause; | |
| obj.cause = wrapper; | |
| } | |
| } | |
| return Object.freeze(obj); | |
| } | |
| // βββ Handler-return shape validator (Finding 1) βββββββββββββββββββββββββββββββ | |
| /** | |
| * Required payload fields per ok:false kind. | |
| * `required` β fields that MUST be present (non-undefined) for the variant to be valid. | |
| * `allowed` β the complete set of allowed fields (including ok, kind). | |
| */ | |
| const _VARIANT_SCHEMA = { | |
| UnknownCommand: { | |
| required: ['command'], | |
| allowed: new Set(['ok', 'kind', 'command']), | |
| }, | |
| InvalidArgs: { | |
| required: ['arg', 'reason'], | |
| allowed: new Set(['ok', 'kind', 'arg', 'reason']), | |
| }, | |
| HandlerRefusal: { | |
| required: ['reason'], | |
| allowed: new Set(['ok', 'kind', 'reason']), | |
| }, | |
| HandlerFailure: { | |
| required: ['message'], | |
| allowed: new Set(['ok', 'kind', 'message', 'cause']), | |
| }, | |
| }; | |
| /** | |
| * Validates a handler-returned { ok: false, ... } result against the typed schema. | |
| * | |
| * Returns null if valid, or a string describing the contract violation. | |
| */ | |
| function _validateErrResult(result) { | |
| const { kind } = result; | |
| const schema = _VARIANT_SCHEMA[kind]; | |
| // Unknown kind β not in the closed enum | |
| if (!schema) { | |
| return `handler returned unknown kind '${String(kind)}': expected one of ${Object.keys(_VARIANT_SCHEMA).join(', ')}`; | |
| } | |
| // Missing required fields | |
| for (const field of schema.required) { | |
| if (result[field] === undefined) { | |
| return (`handler returned malformed Result variant: ` + | |
| `kind '${String(kind)}' requires field '${field}' but it is missing. ` + | |
| `got: ${_safeJson(result)}`); | |
| } | |
| } | |
| // Extraneous fields outside the typed payload | |
| for (const key of Object.keys(result)) { | |
| if (!schema.allowed.has(key)) { | |
| return (`handler returned malformed Result variant: ` + | |
| `kind '${String(kind)}' does not allow field '${key}'. ` + | |
| `expected fields: ${[...schema.allowed].join(', ')}. ` + | |
| `got: ${_safeJson(result)}`); | |
| } | |
| } | |
| return null; // valid | |
| } | |
| /** | |
| * Safe stringify for logger-failure warnings β avoids circular-ref crashes. | |
| */ | |
| function _safeJsonForWarn(value) { | |
| try { | |
| return JSON.stringify(value); | |
| } | |
| catch { | |
| return String(value); | |
| } | |
| } | |
| /** | |
| * Construct a CommandRoutingHub. | |
| */ | |
| function createHub({ cjsRegistry, manifest, logger } = {}) { | |
| const _cjsRegistry = cjsRegistry; | |
| const _manifest = manifest; | |
| // Default to no-op so callers that don't inject a logger get pure-silent behaviour. | |
| // Consumers can opt into the reference impl by importing createDefaultLogger. | |
| const _logger = (logger && typeof logger.onEvent === 'function') | |
| ? logger | |
| : createNoOpLogger(); | |
| /** | |
| * Normalise a HubResult into the DispatchEvent result shape. | |
| * | |
| * HubResult ok path: { ok: true, data } β { kind: 'ok', data } | |
| * HubResult err paths: { ok: false, kind, ...payload } β { kind, ...payload } | |
| */ | |
| function _normaliseResult(hubResult) { | |
| if (hubResult.ok) { | |
| return { kind: 'ok', data: hubResult.data }; | |
| } | |
| // err variant: already has kind + typed payload | |
| // Double-cast through unknown to satisfy strict index-signature check. | |
| return hubResult; | |
| } | |
| /** | |
| * Emit a DispatchEvent to the injected logger. | |
| * Logger errors NEVER propagate β they are caught and emitted as a warn line to stderr. | |
| */ | |
| function _notifyLogger(command, args, hubResult, parentTraceId) { | |
| try { | |
| const eventResult = _normaliseResult(hubResult); | |
| const event = (0, event_cjs_1.makeDispatchEvent)({ command, args, result: eventResult, parentTraceId }); | |
| _logger.onEvent(event); | |
| } | |
| catch (logErr) { | |
| // Logger must never break dispatch. Emit a degraded warn line. | |
| try { | |
| process.stderr.write(_safeJsonForWarn({ | |
| level: 'warn', | |
| source: 'DispatchLogger', | |
| message: 'logger.onEvent failed: ' + String(logErr?.message || logErr), | |
| }) + '\n'); | |
| } | |
| catch { | |
| // If even stderr.write fails, swallow silently β dispatch result is returned below. | |
| } | |
| } | |
| } | |
| /** | |
| * Dispatch a command through the hub. | |
| */ | |
| function dispatch(req) { | |
| const { family, subcommand, args = [], parentTraceId } = req || {}; | |
| const command = subcommand ? `${family} ${subcommand}` : String(family); | |
| let result; | |
| try { | |
| result = _dispatch(req); | |
| } | |
| catch (err) { | |
| if (err instanceof Error) { | |
| result = makeHandlerFailure(err.message, err); | |
| } | |
| else { | |
| // Finding 2: preserve non-Error throwables via a wrapper Error with .thrown | |
| const wrapper = new Error('non-Error thrown: ' + _safeJson(err)); | |
| wrapper.thrown = err; | |
| result = makeHandlerFailure(String(err), wrapper); | |
| } | |
| } | |
| _notifyLogger(command, args, result, parentTraceId); | |
| return result; | |
| } | |
| function _dispatch(req) { | |
| const { family, subcommand, args = [], cwd, raw } = req; | |
| // ββ manifest check ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if (_manifest) { | |
| const knownSubcommands = _manifest[family]; | |
| if (!knownSubcommands) { | |
| return makeUnknownCommand(String(family)); | |
| } | |
| if (subcommand && !knownSubcommands.includes(subcommand)) { | |
| return makeUnknownCommand(`${family} ${subcommand}`); | |
| } | |
| } | |
| return _dispatchCjs({ family, subcommand, args, cwd, raw }); | |
| } | |
| function _dispatchCjs({ family, subcommand, args, cwd, raw }) { | |
| if (!_cjsRegistry) { | |
| return makeUnknownCommand(String(family)); | |
| } | |
| const familyHandlers = _cjsRegistry[family]; | |
| if (!familyHandlers) { | |
| return makeUnknownCommand(String(family)); | |
| } | |
| const handler = subcommand ? familyHandlers[subcommand] : familyHandlers['']; | |
| if (typeof handler !== 'function') { | |
| return makeUnknownCommand(subcommand ? `${family} ${subcommand}` : String(family)); | |
| } | |
| // Invoke the handler. It must return a HubResult or throw. | |
| // If it throws, the outer try/catch in dispatch() catches it. | |
| const result = handler({ family, subcommand, args, cwd, raw }); | |
| // If the handler returned a HubResult, validate ok:false variants against the typed schema. | |
| if (result && typeof result === 'object' && 'ok' in result) { | |
| if (!result.ok) { | |
| // Finding 1: runtime-validate ok:false variant shape; coerce malformed to HandlerFailure | |
| const violation = _validateErrResult(result); | |
| if (violation !== null) { | |
| return makeHandlerFailure('handler returned malformed Result variant: ' + violation, | |
| // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-plus-operands | |
| new Error('expected ' + (result['kind'] ?? '<no kind>') + ', got ' + _safeJson(result))); | |
| } | |
| } | |
| return result; | |
| } | |
| // If the handler returned nothing (undefined), treat as success with no data. | |
| if (result === undefined || result === null) { | |
| return { ok: true, data: null }; | |
| } | |
| // Any other return value is treated as the data payload. | |
| return { ok: true, data: result }; | |
| } | |
| return { dispatch }; | |
| } | |
| module.exports = { | |
| createHub, | |
| ERROR_KINDS, | |
| makeUnknownCommand, | |
| makeInvalidArgs, | |
| makeHandlerRefusal, | |
| makeHandlerFailure, | |
| }; | |