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 .name is 'ViperError', and

  • its .message is 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

Error.parse(str)

Error or undefined

Parse an error string into an Error value

err.component()

string

The subsystem (e.g. 'Viper.ValueInt64')

err.domain()

string

The functional domain (e.g. 'TypeErrors')

err.code()

number

Numeric error code within the domain

err.message()

string

Human-readable description

err.hostname()

string

Host where the error originated

err.processName()

string

Process name

err.explained()

string

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 calling unwrap() on an optional.

  • Check report.hasError() after DSMBuilder.parse() before using the returned definitions.

  • VError.parse() can return undefined; 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.