tftsr-devops_investigation/node_modules/serialize-error/index.js
Shaun Arman 8839075805 feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.

Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)

Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)

DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload

Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 22:36:25 -05:00

222 lines
4.5 KiB
JavaScript

import {errorConstructors} from './error-constructors.js';
export class NonError extends Error {
name = 'NonError';
constructor(message) {
super(NonError._prepareSuperMessage(message));
}
static _prepareSuperMessage(message) {
try {
return JSON.stringify(message);
} catch {
return String(message);
}
}
}
const errorProperties = [
{
property: 'name',
enumerable: false,
},
{
property: 'message',
enumerable: false,
},
{
property: 'stack',
enumerable: false,
},
{
property: 'code',
enumerable: true,
},
{
property: 'cause',
enumerable: false,
},
{
property: 'errors',
enumerable: false,
},
];
const toJsonWasCalled = new WeakSet();
const toJSON = from => {
toJsonWasCalled.add(from);
const json = from.toJSON();
toJsonWasCalled.delete(from);
return json;
};
const newError = name => {
const ErrorConstructor = errorConstructors.get(name) ?? Error;
return ErrorConstructor === AggregateError
? new ErrorConstructor([])
: new ErrorConstructor();
};
// eslint-disable-next-line complexity
const destroyCircular = ({
from,
seen,
to,
forceEnumerable,
maxDepth,
depth,
useToJSON,
serialize,
}) => {
if (!to) {
if (Array.isArray(from)) {
to = [];
} else if (!serialize && isErrorLike(from)) {
to = newError(from.name);
} else {
to = {};
}
}
seen.push(from);
if (depth >= maxDepth) {
return to;
}
if (useToJSON && typeof from.toJSON === 'function' && !toJsonWasCalled.has(from)) {
return toJSON(from);
}
const continueDestroyCircular = value => destroyCircular({
from: value,
seen: [...seen],
forceEnumerable,
maxDepth,
depth,
useToJSON,
serialize,
});
for (const [key, value] of Object.entries(from)) {
if (value && value instanceof Uint8Array && value.constructor.name === 'Buffer') {
to[key] = '[object Buffer]';
continue;
}
// TODO: Use `stream.isReadable()` when targeting Node.js 18.
if (value !== null && typeof value === 'object' && typeof value.pipe === 'function') {
to[key] = '[object Stream]';
continue;
}
if (typeof value === 'function') {
continue;
}
if (!value || typeof value !== 'object') {
// Gracefully handle non-configurable errors like `DOMException`.
try {
to[key] = value;
} catch {}
continue;
}
if (!seen.includes(from[key])) {
depth++;
to[key] = continueDestroyCircular(from[key]);
continue;
}
to[key] = '[Circular]';
}
if (serialize || to instanceof Error) {
for (const {property, enumerable} of errorProperties) {
if (from[property] !== undefined && from[property] !== null) {
Object.defineProperty(to, property, {
value: isErrorLike(from[property]) || Array.isArray(from[property])
? continueDestroyCircular(from[property])
: from[property],
enumerable: forceEnumerable ? true : enumerable,
configurable: true,
writable: true,
});
}
}
}
return to;
};
export function serializeError(value, options = {}) {
const {
maxDepth = Number.POSITIVE_INFINITY,
useToJSON = true,
} = options;
if (typeof value === 'object' && value !== null) {
return destroyCircular({
from: value,
seen: [],
forceEnumerable: true,
maxDepth,
depth: 0,
useToJSON,
serialize: true,
});
}
// People sometimes throw things besides Error objects…
if (typeof value === 'function') {
// `JSON.stringify()` discards functions. We do too, unless a function is thrown directly.
// We intentionally use `||` because `.name` is an empty string for anonymous functions.
return `[Function: ${value.name || 'anonymous'}]`;
}
return value;
}
export function deserializeError(value, options = {}) {
const {maxDepth = Number.POSITIVE_INFINITY} = options;
if (value instanceof Error) {
return value;
}
if (isMinimumViableSerializedError(value)) {
return destroyCircular({
from: value,
seen: [],
to: newError(value.name),
maxDepth,
depth: 0,
serialize: false,
});
}
return new NonError(value);
}
export function isErrorLike(value) {
return Boolean(value)
&& typeof value === 'object'
&& typeof value.name === 'string'
&& typeof value.message === 'string'
&& typeof value.stack === 'string';
}
// Used as a weak check for immediately-passed objects, whereas `isErrorLike` is used for nested values to avoid bad detection
function isMinimumViableSerializedError(value) {
return Boolean(value)
&& typeof value === 'object'
&& typeof value.message === 'string'
&& !Array.isArray(value);
}
export {addKnownErrorConstructor} from './error-constructors.js';