Error Handling¶
When a Viper C++ operation fails, the Node binding raises a regular JavaScript
Error. This chapter explains how to catch, interpret, and recover from those
errors. The Python equivalent is Error Handling; the error model is
identical — only the idiom differs.
The thrown ViperError¶
A Viper runtime error is a plain JS Error. You catch it like any other:
its
.nameis'ViperError', andits
.messageis the structured string[process@host]:Component:Domain:Code:Message.
const { ValueInt64, ValueString } = require('@digitalsubstrate/dsviper');
try {
ValueInt64.cast(new ValueString('hello')); // wrong source type
} catch (e) {
console.log(e.name); // 'ViperError'
console.log(e.message); // '[…]:Viper.ValueInt64:TypeErrors:110:expected int64, got string [cast].'
console.log(e instanceof Error); // true
}
The message format is [process@host]:Component:Domain:Code:Message.
Note
Not every throw from the binding is a ViperError. Binding-level validation
(for example, building a value from an unusable native) surfaces as an ordinary
Node / N-API message, not the structured form. Check e.name === 'ViperError'
to distinguish a Viper runtime error from a JS-side type problem.
Parsing error details¶
To read individual fields, parse the message with the Error value type.
It collides with the global Error, so import it under an alias:
const { Error: VError, ValueInt64, ValueString } = require('@digitalsubstrate/dsviper');
try {
ValueInt64.cast(new ValueString('hello'));
} catch (e) {
const err = VError.parse(e.message); // an Error value, or undefined
if (err) {
console.log(err.component()); // 'Viper.ValueInt64'
console.log(err.domain()); // 'TypeErrors'
console.log(typeof err.code()); // 'number'
console.log(err.message()); // includes 'expected int64'
console.log(err.hostname()); // the host where the error originated
console.log(err.processName()); // the process name
}
}
VError.parse returns undefined when the string is not a structured error —
empty input, free text, or a message missing the leading [...] bracket or
enough :-separated fields. Colons inside the trailing message are preserved.
const { Error: VError } = require('@digitalsubstrate/dsviper');
VError.parse(''); // undefined
VError.parse('not an error at all'); // undefined
VError.parse('a:b:c:1:msg'); // undefined (no brackets)
VError.parse('[p@h]:C:D:7:msg: with: colons')
.message(); // 'msg: with: colons'
The structured string round-trips through the accessors:
const { Error: VError, ValueInt64, ValueString } = require('@digitalsubstrate/dsviper');
let desc;
try {
ValueInt64.cast(new ValueString('hello'));
} catch (e) {
desc = e.message;
}
const err = VError.parse(desc);
const rebuilt = `[${err.processName()}@${err.hostname()}]`
+ `:${err.component()}:${err.domain()}:${err.code()}:${err.message()}`;
console.log(rebuilt === desc); // true
A negative code is preserved as-is:
const { Error: VError } = require('@digitalsubstrate/dsviper');
const err = VError.parse('[p@h]:C:D:-1:negative code');
console.log(err.code()); // -1
console.log(err.message()); // 'negative code'
Error value methods¶
Method |
Return |
Description |
|---|---|---|
|
|
Parse an error string into an Error value |
|
|
The subsystem (e.g. |
|
|
The functional domain (e.g. |
|
|
Numeric error code within the domain |
|
|
Human-readable description |
|
|
Host where the error originated |
|
|
Process name |
|
|
Full formatted error string |
explained() returns a single string containing every field:
const { Error: VError } = require('@digitalsubstrate/dsviper');
VError.parse('[app@server]:MyComp:MyDomain:99:something broke').explained();
// includes 'server', 'app', 'MyComp', and 'something broke'
Labelling the process¶
Error.setProcessName(name) sets the process field stamped into errors raised
afterwards. This is global state — it affects all subsequent errors, so pick a
stable name at startup:
const { Error: VError } = require('@digitalsubstrate/dsviper');
VError.setProcessName('TestApp');
// errors raised from now on report processName() === 'TestApp'
Common error categories¶
Type mismatch errors¶
These occur when a value has the wrong type for the operation — including a
cast to an incompatible type:
const { ValueInt64, ValueString } = require('@digitalsubstrate/dsviper');
try {
ValueInt64.cast(new ValueString('hello'));
} catch (e) {
console.log(e.message); // '...expected int64, got string [cast].'
}
Recovery: check value.type() before casting, or validate inputs before
handing them to a typed constructor. See Types and Values.
Empty-container access¶
Popping an empty set or reading past the end of a vector raises a structured error:
const { ValueSet, TypeSet, ValueVector, TypeVector, Type } = require('@digitalsubstrate/dsviper');
try {
new ValueSet(new TypeSet(Type.INT64)).pop(); // empty set
} catch (e) {
// a ViperError; VError.parse(e.message) is non-null
}
try {
new ValueVector(new TypeVector(Type.INT64)).at(0); // out of range
} catch (e) {
// a ViperError
}
Recovery: check size() before at() / pop(). See Collections.
Empty optional unwrap¶
Unwrapping a nil optional fails. Always check isNil() first:
const { ValueOptional, TypeOptional, Type } = require('@digitalsubstrate/dsviper');
const v = new ValueOptional(new TypeOptional(Type.STRING)); // nil
const content = v.isNil() ? 'default value' : v.unwrap();
Note
ValueOptional is a Viper C++ container, unrelated to JavaScript’s null /
undefined. It wraps a value with nil / non-nil semantics and provides
isNil(), unwrap(), and wrap(). See Structures and Enumerations.
Key not found (not an error)¶
Reading a missing key from a database does not throw. get() returns a
ValueOptional that is nil:
const result = db.get(attachment, key); // always a ValueOptional
const value = result.isNil() ? null : result.unwrap();
Use has() to check existence before unwrapping:
if (db.has(attachment, key)) {
const value = db.get(attachment, key).unwrap();
}
See Database.
DSM parse errors¶
Parsing invalid DSM returns a report instead of throwing, so you can collect every error at once:
const { DSMBuilder } = require('@digitalsubstrate/dsviper');
const builder = new DSMBuilder();
builder.append('test.dsm', `
namespace Test {00000000-0000-0000-0000-000000000001} {
struct A { invalid_type1 f1; };
};
`);
const [report, dsmDefs, defs] = builder.parse();
if (report.hasError()) {
const errors = [...report.errors()];
console.log(errors[0].message()); // mentions 'invalid_type1'
}
See DSM Processing.
Best practices¶
Check
e.name === 'ViperError'to tell a Viper runtime error apart from a JS-side or binding-level failure.Check
isNil()before callingunwrap()on an optional.Check
report.hasError()afterDSMBuilder.parse()before using the returned definitions.VError.parse()can returnundefined; guard the result before calling accessors.
Debugging tips¶
Enable verbose logging¶
Use Viper C++’s logging system to trace operations. A logger is constructed with
a numeric threshold; logger.logging() returns the logging facade with one
method per level:
const { LoggerConsole } = require('@digitalsubstrate/dsviper');
// Level thresholds are plain numbers (ALL=0, DEBUG=10, INFO=20,
// WARNING=30, ERROR=40, CRITICAL=50).
const logger = new LoggerConsole(20); // INFO and above
const logging = logger.logging();
logging.info('Starting database operation');
logging.error('Operation failed');
The facade exposes log(level, message) plus debug(), info(), warning(),
error(), and critical(); messages below the logger’s threshold are filtered
out.
Remote errors¶
When working against a remote database or service, the host and process
fields of the structured message identify where the failure occurred:
[db-process@remote-server]:Viper:Database:3:Connection timeout
Parse it with VError.parse() and read hostname() / processName() to route
the diagnostic. See Database.