DSM Processing¶
This chapter covers building runtime definitions in memory and loading DSM (Digital Substrate Model) files from Node.js. This is the dynamic path: the type universe is constructed or parsed at runtime, with no code generation. The static path — importing a typed TypeScript package produced by Kibo — is a separate route, planned for a later release.
This is the Node twin of the Python DSM Processing page.
const {
Definitions, NameSpace, ValueUUId, Type,
TypeStructureDescriptor, TypeEnumerationDescriptor,
} = require('@digitalsubstrate/dsviper');
Building Definitions in Memory¶
Definitions is the runtime registry of the type universe. You add types to it
directly, then freeze the result with const() — an immutable snapshot that a
database adopts or that you introspect and serialize.
Tip
Every namespaced type lives under a NameSpace, built from a fresh UUID and a
name: new NameSpace(ValueUUId.create(), 'Tuto').
Concepts¶
A concept is an identity type. createConcept returns its runtime handle:
const ns = new NameSpace(ValueUUId.create(), 'Tuto');
const defs = new Definitions();
const user = defs.createConcept(ns, 'User', 'A user.');
user.typeName().name(); // 'User'
user.documentation(); // 'A user.'
user.parent(); // undefined — no parent
Pass a parent concept as the fourth argument to declare inheritance:
const admin = defs.createConcept(ns, 'Admin', '', user);
admin.parent().equals(user); // true
Clubs and Memberships¶
A club groups concepts. Add members with createMembership:
const a = defs.createConcept(ns, 'A');
const b = defs.createConcept(ns, 'B');
const club = defs.createClub(ns, 'Group', 'A grouping.');
defs.createMembership(club, a);
defs.createMembership(club, b);
club.members().length; // 2
club.isMember(a); // true
Enumerations¶
Describe an enumeration with a TypeEnumerationDescriptor, then register it.
Each case is added with addCase(name, documentation?); a duplicate case name
throws:
const de = new TypeEnumerationDescriptor('Status', 'Account status.');
de.addCase('pending', 'Awaiting activation.');
de.addCase('active');
const status = defs.createEnumeration(ns, de);
status.cases().length; // 2
status.cases()[0].name(); // 'pending'
status.cases()[0].documentation(); // 'Awaiting activation.'
Structures¶
Describe a structure with a TypeStructureDescriptor and addField. A field
takes either a Type or a default value (which fixes both the field type and
its default):
const { TypeVector, ValueVector } = require('@digitalsubstrate/dsviper');
const ds = new TypeStructureDescriptor('Login', 'Login record.');
ds.addField('nickname', Type.STRING);
ds.addField('tags', new ValueVector(new TypeVector(Type.INT64), [1n, 2n, 3n]));
const login = defs.createStructure(ns, ds);
login.fields().length; // 2
login.fields()[1].name(); // 'tags'
[...login.fields()[1].defaultValue()]; // [1n, 2n, 3n] (INT64 → bigint)
Note
Structures have no attribute protocol: there is no value.tags. Construct and
read structure values via object construction and .at / .set — see
Structures and Enumerations.
Attachments¶
An attachment is a typed map from a key type to a document type. The key type is
a concept, a club, or a built-in abstraction such as Type.ANY_CONCEPT; the
document type is any Type:
const login_att = defs.createAttachment(ns, 'login', user, login);
login_att.keyType().equals(user); // true
login_att.documentType().equals(login); // true
// A concept-agnostic attachment keyed by ANY_CONCEPT, holding an INT64:
const score = defs.createAttachment(ns, 'score', Type.ANY_CONCEPT, Type.INT64);
Freezing and Seeding a Database¶
const() returns the immutable snapshot. A database adopts it via
extendDefinitions:
const { CommitDatabase } = require('@digitalsubstrate/dsviper');
const constDefs = defs.const(); // DefinitionsConst
const db = CommitDatabase.createInMemory();
db.extendDefinitions(constDefs);
constDefs.isEqual(db.definitions()); // true
db.close();
See Database for the full write/read lifecycle that follows.
Loading DSM Files¶
DSMBuilder.assemble(path) reads a .dsm file (or a directory of them, parsed
as one unit). parse() validates syntax and semantics and returns a triple:
const { DSMBuilder } = require('@digitalsubstrate/dsviper');
const [report, dsmDefs, defs] = DSMBuilder.assemble('model.dsm').parse();
report.hasError(); // false
Return Value |
Type |
Description |
|---|---|---|
|
|
Errors and warnings |
|
|
Structured DSM data |
|
|
Runtime definitions (ready to adopt) |
Handling Errors¶
report.errors() yields the diagnostics; iterate before trusting dsmDefs or
defs:
const [report, dsmDefs, defs] = DSMBuilder.assemble('model.dsm').parse();
if (report.hasError()) {
for (const error of report.errors()) {
console.error(error.message());
}
} else {
console.log('Parse successful!');
}
A parsed DSMDefinitions converts to a runtime snapshot at any point with
toDefinitions(), the bridge between the structured form and the runtime
registry:
const runtime = dsmDefs.toDefinitions(); // a DefinitionsConst
DSM Introspection¶
DSMDefinitions exposes the parsed model as structured collections —
concepts(), clubs(), enumerations(), structures(), attachments(),
plus functionPools() and attachmentFunctionPools().
Concepts¶
const concept = dsmDefs.concepts()[0];
concept.typeName().name(); // e.g. 'User'
concept.documentation();
concept.parent(); // a DSMTypeReference, or undefined at the root
concept.runtimeId();
For a model with inheritance, parent() returns a DSMTypeReference to the
parent concept; follow parent().typeName().name() up the chain.
Structures and Fields¶
const struct = dsmDefs.structures().find(s => s.typeName().name() === 'Login');
struct.fields().map(f => f.name()); // ['nickname', 'password']
const field = struct.fields()[0];
field.name(); // 'nickname'
field.type().typeName().name(); // 'string'
field.documentation();
field.defaultValue(); // a DSMLiteralValue
A field’s type() is a DSMType. Primitives and named types come back as
DSMTypeReference (read type.typeName().name()); container types are distinct
subclasses you can branch on with instanceof:
const { DSMTypeVector, DSMTypeMap, DSMTypeKey } = require('@digitalsubstrate/dsviper');
const t = field.type();
if (t instanceof DSMTypeVector) {
t.elementType().typeName().name(); // the element type
} else if (t instanceof DSMTypeMap) {
t.keyType().typeName().name(); // map key
t.elementType().typeName().name(); // map value
} else if (t instanceof DSMTypeKey) {
t.elementType().typeName().name(); // the referenced concept/club
}
Other container wrappers include DSMTypeSet, DSMTypeOptional,
DSMTypeTuple and DSMTypeVariant (.types() over their members),
DSMTypeXArray, and the math types DSMTypeVec (.size()) and DSMTypeMat
(.rows() / .columns()). Container element types nest, so a
vector<vector<int64>> is a DSMTypeVector whose elementType() is another
DSMTypeVector.
Enumerations¶
const enumeration = dsmDefs.enumerations()[0];
enumeration.typeName().name(); // e.g. 'Status'
enumeration.members().map(c => c.name()); // ['pending', 'active', 'completed']
enumeration.members()[0].documentation();
Clubs¶
const club = dsmDefs.clubs()[0];
club.members().map(m => m.typeName().name()); // member concept names
Attachments¶
const att = dsmDefs.attachments()[0];
att.identifier(); // a string id that round-trips queryAttachment
att.keyType(); // a DSMType
att.documentType(); // a DSMType
Inspecting Runtime Definitions¶
DefinitionsInspector wraps a DefinitionsConst snapshot and answers
type-name / identifier queries without rebuilding anything:
const { DefinitionsInspector } = require('@digitalsubstrate/dsviper');
const insp = new DefinitionsInspector(defs); // defs is a DefinitionsConst
insp.conceptTypeNames(); // array of TypeName
insp.clubTypeNames();
insp.enumerationTypeNames();
insp.structureTypeNames();
insp.attachmentIdentifiers(); // array of attachment id strings
insp.nameSpaces(); // array of NameSpace
Lookups come in two flavours. query* returns the type or undefined on a
miss; check* throws a ViperError on a miss:
const { TypeConcept } = require('@digitalsubstrate/dsviper');
const c = insp.queryConcept(someTypeName); // TypeConcept | undefined
c instanceof TypeConcept; // true on a hit
insp.queryEnumeration(conceptTypeName); // undefined — not an enumeration
insp.checkConcept(someTypeName); // returns the type, or throws
// Attachments are addressed by an identifier string from attachmentIdentifiers():
insp.queryAttachment(insp.attachmentIdentifiers()[0]); // an Attachment
insp.queryAttachment('NOPE'); // undefined
representation(typeName) renders a type to a readable string. The same
query* / check* pair (keyed by runtime id rather than type name) is also
available directly on DefinitionsConst.
Serialization¶
DSMDefinitions can be serialized for distribution and round-trips losslessly.
JSON¶
The canonical on-disk form consumed by Kibo and dsm_util. jsonEncode()
returns a string; DSMDefinitions.jsonDecode(str) rebuilds it. Encode → decode
→ re-encode is byte-stable:
const { DSMDefinitions } = require('@digitalsubstrate/dsviper');
const json = dsmDefs.jsonEncode();
const restored = DSMDefinitions.jsonDecode(json);
restored.jsonEncode() === json; // true
DSMDefinitions.jsonDecode('invalid json{{{'); // throws a ViperError
A malformed payload throws a ViperError whose message quotes a JSON path
locating the offending node (e.g. structures[2].fields[0].type).
Binary and BSON¶
encode() returns a compact binary ValueBlob (DSMDefinitions.decode(blob)
restores it); bsonEncode() / bsonDecode() is the BSON equivalent. All three
codecs cross-convert — a binary blob decoded and re-emitted as JSON matches a
direct JSON encode:
const blob = dsmDefs.encode(); // a ValueBlob
DSMDefinitions.decode(blob).jsonEncode() === dsmDefs.jsonEncode(); // true
The runtime registry itself also has a binary codec: defs.encode() returns a
blob and Definitions.decode(blob) restores a Definitions.
Generating DSM Text¶
toDsm() reconstructs DSM source from a parsed model. It takes positional
booleans — toDsm(showDocumentation, showRuntimeId, html):
dsmDefs.toDsm(); // default render
dsmDefs.toDsm(true, true, false); // append // Runtime ID comments
dsmDefs.toDsm(true, false, true); // HTML-wrapped output
A fourth argument filters the rendered attachment set to a given array of
DSMAttachments; passing a non-array or a non-attachment element throws.