@vizualmodel/vmblu-cli 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/commands/init/index.js +4 -2
- package/commands/init/init-project.js +9 -4
- package/commands/profile/profile.bundle.js +29 -20
- package/package.json +2 -1
- package/templates/0.8.3/profile.schema.json +74 -0
- package/templates/0.8.3/seed.md +17 -0
- package/templates/0.8.3/vmblu.annex.md +136 -0
- package/templates/0.8.3/vmblu.schema.json +268 -0
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# CLI for vmblu
|
|
2
2
|
This folder contains the CLI commands that are available for vmblu.
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
## Folder layout
|
|
6
5
|
|
|
7
6
|
```txt
|
|
@@ -12,7 +11,7 @@ vmblu/
|
|
|
12
11
|
profile/
|
|
13
12
|
migrate/
|
|
14
13
|
templates/
|
|
15
|
-
|
|
14
|
+
x.y.z/ # a directory per version x.y.z
|
|
16
15
|
vmblu.schema.json
|
|
17
16
|
vmblu.annex.md
|
|
18
17
|
seed.md
|
|
@@ -25,7 +24,7 @@ vmblu/
|
|
|
25
24
|
|
|
26
25
|
## Add more commands
|
|
27
26
|
|
|
28
|
-
Create commands/migrate/index.js with the same export shape { command, describe, builder, handler }. The router auto-discovers it
|
|
27
|
+
Create commands/migrate/index.js with the same export shape { command, describe, builder, handler }. The router auto-discovers it.
|
|
29
28
|
|
|
30
29
|
## Dev/test workflow
|
|
31
30
|
|
package/commands/init/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { initProject } from './init-project.js';
|
|
5
|
+
import pckg from '../../package.json' assert { type: 'json' };
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
9
|
|
|
@@ -9,7 +11,7 @@ export const command = 'init <folder name>';
|
|
|
9
11
|
export const describe = 'Scaffold an empty vmblu project';
|
|
10
12
|
export const builder = [
|
|
11
13
|
{ flag: '--name <project>', desc: 'Project name (default: folder name)' },
|
|
12
|
-
{ flag: '--schema <ver>', desc: 'Schema version (default:
|
|
14
|
+
{ flag: '--schema <ver>', desc: 'Schema version (default: latest version)' },
|
|
13
15
|
{ flag: '--force', desc: 'Overwrite existing files' },
|
|
14
16
|
{ flag: '--dry-run', desc: 'Show actions without writing' }
|
|
15
17
|
];
|
|
@@ -28,7 +30,7 @@ export const handler = async (argv) => {
|
|
|
28
30
|
|
|
29
31
|
const targetDir = path.resolve(args._[0] || '.');
|
|
30
32
|
const projectName = args.name || path.basename(targetDir);
|
|
31
|
-
const schemaVersion = args.schema ||
|
|
33
|
+
const schemaVersion = args.schema || pckg.schemaVersion;
|
|
32
34
|
|
|
33
35
|
await initProject({
|
|
34
36
|
targetDir,
|
|
@@ -6,6 +6,11 @@ import path from 'path';
|
|
|
6
6
|
//import crypto from 'crypto';
|
|
7
7
|
import { makePackageJson } from './make-package-json.js';
|
|
8
8
|
|
|
9
|
+
// Get the versions
|
|
10
|
+
import pckg from '../../package.json' assert { type: 'json' };
|
|
11
|
+
const SCHEMA_VERSION = pckg.schemaVersion
|
|
12
|
+
const CLI_VERSION = pckg.version
|
|
13
|
+
|
|
9
14
|
function rel(from, to) {
|
|
10
15
|
return path.posix.join(...path.relative(from, to).split(path.sep));
|
|
11
16
|
}
|
|
@@ -50,7 +55,7 @@ function defaultModel(projectName) {
|
|
|
50
55
|
const now = new Date().toISOString();
|
|
51
56
|
return JSON.stringify({
|
|
52
57
|
header: {
|
|
53
|
-
version:
|
|
58
|
+
version: SCHEMA_VERSION,
|
|
54
59
|
created: now,
|
|
55
60
|
saved: now,
|
|
56
61
|
utc: now,
|
|
@@ -73,7 +78,7 @@ function defaultModel(projectName) {
|
|
|
73
78
|
function defaultDoc(projectName) {
|
|
74
79
|
const now = new Date().toISOString();
|
|
75
80
|
return JSON.stringify({
|
|
76
|
-
version:
|
|
81
|
+
version: CLI_VERSION,
|
|
77
82
|
generatedAt: now,
|
|
78
83
|
entries: {}
|
|
79
84
|
}, null, 2);
|
|
@@ -145,7 +150,7 @@ async function initProject(opts) {
|
|
|
145
150
|
const {
|
|
146
151
|
targetDir,
|
|
147
152
|
projectName = path.basename(opts.targetDir),
|
|
148
|
-
schemaVersion =
|
|
153
|
+
schemaVersion = SCHEMA_VERSION,
|
|
149
154
|
force = false,
|
|
150
155
|
dryRun = false,
|
|
151
156
|
templatesDir = path.join(__dirname, '..', 'templates'),
|
|
@@ -246,7 +251,7 @@ async function initProject(opts) {
|
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
// 5) Make the package file
|
|
249
|
-
makePackageJson({ absTarget, projectName, force, dryRun, addCliDep: true, cliVersion: "^
|
|
254
|
+
makePackageJson({ absTarget, projectName, force, dryRun, addCliDep: true, cliVersion: "^" + CLI_VERSION }, ui);
|
|
250
255
|
|
|
251
256
|
// 6) Final tree hint
|
|
252
257
|
ui.info(`\nScaffold complete${dryRun ? ' (dry run)' : ''}:\n` +
|
|
@@ -2875,7 +2875,7 @@ async readSourceMap() {
|
|
|
2875
2875
|
|
|
2876
2876
|
// get the full path
|
|
2877
2877
|
const fullPath = this.arl?.getFullPath();
|
|
2878
|
-
|
|
2878
|
+
console.log('FULLPATH', fullPath);
|
|
2879
2879
|
// check
|
|
2880
2880
|
if (!fullPath) return null
|
|
2881
2881
|
|
|
@@ -4054,8 +4054,6 @@ const keyboardHandling$1 = {
|
|
|
4054
4054
|
|
|
4055
4055
|
// and redraw
|
|
4056
4056
|
this.redraw();
|
|
4057
|
-
|
|
4058
|
-
console.log('hello baby');
|
|
4059
4057
|
}
|
|
4060
4058
|
},
|
|
4061
4059
|
|
|
@@ -4250,7 +4248,7 @@ const messageHandling = {
|
|
|
4250
4248
|
//const appPath = doc.target.application?.userPath ?? Path.changeExt(doc.model.arl.userPath, 'js')
|
|
4251
4249
|
const appPath =
|
|
4252
4250
|
doc.target.library?.userPath ??
|
|
4253
|
-
removeExt(doc.model.arl.userPath) + '
|
|
4251
|
+
removeExt(doc.model.arl.userPath) + '.app.js';
|
|
4254
4252
|
|
|
4255
4253
|
// request the path for the save as operation
|
|
4256
4254
|
this.tx.send('show app path', {
|
|
@@ -12227,7 +12225,12 @@ showProfile: {
|
|
|
12227
12225
|
const profile = pin.is.input ? editor.doc.model.getInputPinProfile(pin) : editor.doc.model.getOutputPinProfile(pin);
|
|
12228
12226
|
|
|
12229
12227
|
// check
|
|
12230
|
-
if (!profile)
|
|
12228
|
+
if (!profile) {
|
|
12229
|
+
|
|
12230
|
+
console.log(`NO PROFILE ${pin.name}`);
|
|
12231
|
+
|
|
12232
|
+
return
|
|
12233
|
+
}
|
|
12231
12234
|
|
|
12232
12235
|
// show the profile
|
|
12233
12236
|
editor.tx.send('pin profile',{pos, pin, profile,
|
|
@@ -14037,6 +14040,10 @@ Editor.prototype = {
|
|
|
14037
14040
|
};
|
|
14038
14041
|
Object.assign(Editor.prototype, mouseHandling$1, keyboardHandling$1, messageHandling, undoRedoHandling);
|
|
14039
14042
|
|
|
14043
|
+
/**
|
|
14044
|
+
* @node editor editor
|
|
14045
|
+
*/
|
|
14046
|
+
|
|
14040
14047
|
function placePopup(pos) {
|
|
14041
14048
|
return {x: pos.x - 15, y:pos.y + 10}
|
|
14042
14049
|
}
|
|
@@ -14046,7 +14053,6 @@ const nodeClickHandling = {
|
|
|
14046
14053
|
showExportForm(pos) {
|
|
14047
14054
|
|
|
14048
14055
|
const node = this;
|
|
14049
|
-
editor.tx;
|
|
14050
14056
|
|
|
14051
14057
|
// send the show link path
|
|
14052
14058
|
editor.tx.send("show link",{
|
|
@@ -14062,7 +14068,6 @@ const nodeClickHandling = {
|
|
|
14062
14068
|
showLinkForm(pos) {
|
|
14063
14069
|
|
|
14064
14070
|
const node = this;
|
|
14065
|
-
const tx = editor.tx;
|
|
14066
14071
|
|
|
14067
14072
|
// check what path to show - show no path if from the main model
|
|
14068
14073
|
const linkPath = (node.link && (node.link.model != editor.doc?.model)) ? node.link.model?.arl.userPath : '';
|
|
@@ -14090,7 +14095,7 @@ const nodeClickHandling = {
|
|
|
14090
14095
|
editor.doEdit('changeLink',{node, lName: newName, userPath: newPath});
|
|
14091
14096
|
|
|
14092
14097
|
// open the file if the link is to an outside file !
|
|
14093
|
-
if (node.link.model?.arl && (node.link.model != editor.doc?.model)) tx.send('open document',node.link.model.arl);
|
|
14098
|
+
if (node.link.model?.arl && (node.link.model != editor.doc?.model)) editor.tx.send('open document',node.link.model.arl);
|
|
14094
14099
|
},
|
|
14095
14100
|
cancel:()=>{}
|
|
14096
14101
|
});
|
|
@@ -14099,7 +14104,6 @@ const nodeClickHandling = {
|
|
|
14099
14104
|
iconClick(view, icon, pos) {
|
|
14100
14105
|
|
|
14101
14106
|
const node = this;
|
|
14102
|
-
const tx = editor.tx;
|
|
14103
14107
|
|
|
14104
14108
|
// move the popup a bit away from the icon
|
|
14105
14109
|
const newPos = placePopup(pos);
|
|
@@ -14119,7 +14123,7 @@ const nodeClickHandling = {
|
|
|
14119
14123
|
const factoryPath = node.factory.arl ? node.factory.arl.userPath : '';
|
|
14120
14124
|
|
|
14121
14125
|
// show the factory
|
|
14122
|
-
tx.send("show factory",{ title: 'Factory for ' + node.name,
|
|
14126
|
+
editor.tx.send("show factory",{ title: 'Factory for ' + node.name,
|
|
14123
14127
|
name: factoryName,
|
|
14124
14128
|
path: factoryPath,
|
|
14125
14129
|
pos: newPos,
|
|
@@ -14138,7 +14142,7 @@ const nodeClickHandling = {
|
|
|
14138
14142
|
const arl = node.factory.arl ?? editor.doc.resolve('./index.js');
|
|
14139
14143
|
|
|
14140
14144
|
// open the file
|
|
14141
|
-
tx.send('open source file',{arl});
|
|
14145
|
+
editor.tx.send('open source file',{arl});
|
|
14142
14146
|
},
|
|
14143
14147
|
cancel:()=>{}
|
|
14144
14148
|
});
|
|
@@ -14175,7 +14179,7 @@ const nodeClickHandling = {
|
|
|
14175
14179
|
|
|
14176
14180
|
case 'cog':
|
|
14177
14181
|
|
|
14178
|
-
tx.send("settings",{ title:'Settings for ' + node.name,
|
|
14182
|
+
editor.tx.send("settings",{ title:'Settings for ' + node.name,
|
|
14179
14183
|
pos: newPos,
|
|
14180
14184
|
json: node.sx,
|
|
14181
14185
|
ok: (sx) => editor.doEdit("changeNodeSettings",{node, sx})
|
|
@@ -14184,7 +14188,7 @@ const nodeClickHandling = {
|
|
|
14184
14188
|
|
|
14185
14189
|
case 'pulse':
|
|
14186
14190
|
|
|
14187
|
-
tx.send("runtime settings",{ title:'Runtime settings for ' + node.name,
|
|
14191
|
+
editor.tx.send("runtime settings",{ title:'Runtime settings for ' + node.name,
|
|
14188
14192
|
pos: newPos,
|
|
14189
14193
|
dx: node.dx,
|
|
14190
14194
|
ok: (dx) => editor.doEdit("changeNodeDynamics",{node, dx})
|
|
@@ -14194,7 +14198,7 @@ const nodeClickHandling = {
|
|
|
14194
14198
|
case 'comment':
|
|
14195
14199
|
|
|
14196
14200
|
// save the node hit
|
|
14197
|
-
tx.send("node comment", { header: 'Comment for ' + node.name,
|
|
14201
|
+
editor.tx.send("node comment", { header: 'Comment for ' + node.name,
|
|
14198
14202
|
pos: newPos,
|
|
14199
14203
|
uid: node.uid,
|
|
14200
14204
|
text: node.prompt ?? '',
|
|
@@ -14208,7 +14212,6 @@ const nodeClickHandling = {
|
|
|
14208
14212
|
iconCtrlClick(view,icon, pos) {
|
|
14209
14213
|
|
|
14210
14214
|
const node = this;
|
|
14211
|
-
const tx = editor.tx;
|
|
14212
14215
|
|
|
14213
14216
|
switch (icon.type) {
|
|
14214
14217
|
|
|
@@ -14216,7 +14219,7 @@ const nodeClickHandling = {
|
|
|
14216
14219
|
case 'lock': {
|
|
14217
14220
|
|
|
14218
14221
|
// open the file if it points to an external model
|
|
14219
|
-
if (node.link?.model?.arl && (node.link.model != editor.doc?.model)) tx.send('open document',node.link.model.arl);
|
|
14222
|
+
if (node.link?.model?.arl && (node.link.model != editor.doc?.model)) editor.tx.send('open document',node.link.model.arl);
|
|
14220
14223
|
}
|
|
14221
14224
|
break
|
|
14222
14225
|
|
|
@@ -14229,7 +14232,7 @@ const nodeClickHandling = {
|
|
|
14229
14232
|
const arl = node.factory.arl ?? editor.doc.resolve('./index.js');
|
|
14230
14233
|
|
|
14231
14234
|
// request to open the file
|
|
14232
|
-
if (arl) tx.send("open source file", {arl});
|
|
14235
|
+
if (arl) editor.tx.send("open source file", {arl});
|
|
14233
14236
|
}
|
|
14234
14237
|
break
|
|
14235
14238
|
|
|
@@ -17071,7 +17074,7 @@ cook( raw ) {
|
|
|
17071
17074
|
for (const widget of this.widgets) if (widget.wid && (widget.wid > this.widGenerator)) this.widGenerator = widget.wid;
|
|
17072
17075
|
|
|
17073
17076
|
// if there are widgets with a wid of zero, correct this
|
|
17074
|
-
for (const widget of this.widgets) if (widget.wid
|
|
17077
|
+
for (const widget of this.widgets) if (widget.wid == 0) widget.wid = this.generateWid();
|
|
17075
17078
|
},
|
|
17076
17079
|
|
|
17077
17080
|
cookPin(raw) {
|
|
@@ -23326,7 +23329,13 @@ function getEnclosingHandlerName(callExpression) {
|
|
|
23326
23329
|
return null;
|
|
23327
23330
|
}
|
|
23328
23331
|
|
|
23329
|
-
|
|
23332
|
+
var version = "0.3.2";
|
|
23333
|
+
var pckg = {
|
|
23334
|
+
version: version};
|
|
23335
|
+
|
|
23336
|
+
const PROFILE_VERSION = pckg.version;
|
|
23337
|
+
|
|
23338
|
+
// const PROFILE_VERSION = '0.2';
|
|
23330
23339
|
|
|
23331
23340
|
// The main function for the profile tool
|
|
23332
23341
|
async function profile(argv = process.argv.slice(2)) {
|
|
@@ -23413,7 +23422,7 @@ async function profile(argv = process.argv.slice(2)) {
|
|
|
23413
23422
|
|
|
23414
23423
|
// and write the output to that file
|
|
23415
23424
|
const output = {
|
|
23416
|
-
version:
|
|
23425
|
+
version: PROFILE_VERSION,
|
|
23417
23426
|
generatedAt,
|
|
23418
23427
|
entries: rxtx
|
|
23419
23428
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "@vizual_model/cli/templates/0.8.3/profile.schema.json",
|
|
4
|
+
"title": "vmblu Source Documentation (grouped by node)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "generatedAt", "entries"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": { "type": "string" },
|
|
9
|
+
"generatedAt": { "type": "string", "format": "date-time" },
|
|
10
|
+
"entries": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": { "$ref": "#/$defs/Entry" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"$defs": {
|
|
16
|
+
"Param": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"required": ["name", "type", "description"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"name": { "type": "string" },
|
|
22
|
+
"type": { "type": "string" },
|
|
23
|
+
"description": { "type": "string" }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"Handle": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": false,
|
|
29
|
+
"required": ["pin", "handler", "file", "line", "summary", "returns", "examples", "params"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"pin": { "type": "string" },
|
|
32
|
+
"handler": { "type": "string" },
|
|
33
|
+
"file": { "type": "string" },
|
|
34
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
35
|
+
"summary": { "type": "string" },
|
|
36
|
+
"returns": { "type": "string" },
|
|
37
|
+
"examples": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": { "type": "string" }
|
|
40
|
+
},
|
|
41
|
+
"params": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": { "$ref": "#/$defs/Param" }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"Transmit": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["pin", "file", "line"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"pin": { "type": "string" },
|
|
53
|
+
"file": { "type": "string" },
|
|
54
|
+
"line": { "type": "integer", "minimum": 1 }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"Entry": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"additionalProperties": false,
|
|
60
|
+
"required": ["node", "handles", "transmits"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"node": { "type": "string" },
|
|
63
|
+
"handles": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": { "$ref": "#/$defs/Handle" }
|
|
66
|
+
},
|
|
67
|
+
"transmits": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"items": { "$ref": "#/$defs/Transmit" }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Session Seed (System Prompt)
|
|
2
|
+
|
|
3
|
+
vmblu (Vizual Model Blueprint) is a graphical editor that maintains a visual, runnable model of a software system.
|
|
4
|
+
vmblu models software as interconnected nodes that pass messages via pins.
|
|
5
|
+
|
|
6
|
+
The model has a well defined format described by a schema. An additional annex gives semantic background information about the schema.
|
|
7
|
+
The parameter profiles of messages and where messages are received and sent in the actual source code, are stored in a second file, the profile file.
|
|
8
|
+
The profile file is generated automatically by vmblu and is only to be consulted, not written, at the start of a project it does not yet exist
|
|
9
|
+
|
|
10
|
+
You are an expert **architecture + code copilot** for **vmblu** .
|
|
11
|
+
You can find the location of the model file, the model schema, the model annex, the profile file and the profile schema in the 'manifest.json' file of this project. Read these files.
|
|
12
|
+
|
|
13
|
+
The location of all other files in the project can be found via the model file.
|
|
14
|
+
|
|
15
|
+
Your job is to co-design the architecture and the software for the system.
|
|
16
|
+
For modifications of the model, always follow the schema.
|
|
17
|
+
If the profile does not exist yet or does notcontain profile information it could be that the code for the node has not been written yet, this should not stop you from continuing.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Annex A: Semantic Clarifications for vmblu v0.8
|
|
2
|
+
|
|
3
|
+
This annex captures subtle semantic points that are not fully enforceable in the JSON Schema but are critical for correct usage of the vmblu format by both humans and LLMs.
|
|
4
|
+
|
|
5
|
+
## A.1 Nodes
|
|
6
|
+
|
|
7
|
+
A source node groups the functionality of a system that belongs logically together. A source node can represent a UI element, the access to a database, a login procedure, a 3D scene list etc.
|
|
8
|
+
|
|
9
|
+
The name of a node should be meaningful and unique inside a group node.
|
|
10
|
+
|
|
11
|
+
## A.2 Interface names and Pin names
|
|
12
|
+
|
|
13
|
+
- Pins are grouped in **interfaces**
|
|
14
|
+
- There can only be one anonymous interface (name is ""), this is acceptable for nodes that have only a few pins.
|
|
15
|
+
- Group pins together into meaningful interfaces
|
|
16
|
+
- It is good practice to start a pin name with the name of the interface separated by a period or hyphen.
|
|
17
|
+
|
|
18
|
+
## A.3 Handlers
|
|
19
|
+
|
|
20
|
+
Every **input** or **reply** pin corresponds to a message handler in the node implementation.
|
|
21
|
+
|
|
22
|
+
The naming convention for a handler is as follows:
|
|
23
|
+
|
|
24
|
+
- Handler name is `on<PinNameInCamelCase>`.
|
|
25
|
+
- Example:
|
|
26
|
+
- Pin: `"name": "saveMessage", "kind": "input"`
|
|
27
|
+
- Handler: `onSaveMessage(payload)`
|
|
28
|
+
|
|
29
|
+
This uniform convention ensures LLMs and the editor can always map pins to their corresponding handler.
|
|
30
|
+
|
|
31
|
+
Do not return a value from a handler, it is ignored.
|
|
32
|
+
|
|
33
|
+
## A.4 Request / Reply Semantics
|
|
34
|
+
|
|
35
|
+
A Request/Reply connection allows to group a message and the response to that message in one exchange.
|
|
36
|
+
|
|
37
|
+
- A **request pin** is an **output pin**.
|
|
38
|
+
- It is used by the requester node to initiate a request with `tx.request('pinName', payload)`.
|
|
39
|
+
- This function returns a **Promise** which resolves when the callee replies. The **Promise** is generated and managed by the runtime.
|
|
40
|
+
|
|
41
|
+
- A **reply pin** is an **input pin** on the callee.
|
|
42
|
+
- It receives the request payload and has a handler like any input pin.
|
|
43
|
+
- Inside this handler, the callee must call `tx.reply(payload)` to respond to the requester.
|
|
44
|
+
- The runtime delivers this reply on the backchannel and resolves the requester’s Promise.
|
|
45
|
+
- Note that the handler return value is ignored, optional async only to await internal work.
|
|
46
|
+
- If tx.reply is not called, the request promise will simply time out.
|
|
47
|
+
|
|
48
|
+
- **Connections**:
|
|
49
|
+
- Normally, `request` pins connect to `reply` pins.
|
|
50
|
+
- It is also valid to connect a `request` pin to an `input` pin (e.g. for logging or monitoring), but in that case no reply is sent.
|
|
51
|
+
|
|
52
|
+
Typical use of request/reply
|
|
53
|
+
|
|
54
|
+
The requesting node issues a request and then waits for the reply
|
|
55
|
+
```js
|
|
56
|
+
tx.request('pinName', payload).then ( replyPayload => {
|
|
57
|
+
|
|
58
|
+
// handle the reply from the other node
|
|
59
|
+
...
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The receiving node has a handler for the request
|
|
64
|
+
```js
|
|
65
|
+
onPinName(payloed) {
|
|
66
|
+
// does some processing
|
|
67
|
+
...
|
|
68
|
+
// and replies to the requesting node
|
|
69
|
+
tx.reply(replyPayload)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## A.5 Factory Function Signature
|
|
74
|
+
|
|
75
|
+
A source node references its implementation via a **factory** object (`path` + `function`).
|
|
76
|
+
The factory function is called by the runtime to create the node instance.
|
|
77
|
+
|
|
78
|
+
The runtime will detect if the factory function is a generator function or a class name and will call the factory function in that case as follows: `new factoryFunction(...)`
|
|
79
|
+
|
|
80
|
+
In order to let documentation tools find the handlers of a node, add a *node* JSdoc tag in the file where the handlers are defined.
|
|
81
|
+
The tage remains valid until the end of the file or until a new node tag is defined.
|
|
82
|
+
|
|
83
|
+
- Signature:
|
|
84
|
+
```js
|
|
85
|
+
/**
|
|
86
|
+
* @node node name
|
|
87
|
+
*/
|
|
88
|
+
export function createMyNode( tx, sx ) { ... }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- tx: object exposing runtime message functions (send, request, reply).
|
|
92
|
+
- sx: arbitrary initialization data supplied by the model. sx can be null.
|
|
93
|
+
- rx: not passed to the node. Runtime-only directives; used by the runtime to decide how/where to host the node (e.g. worker thread, debug flags).
|
|
94
|
+
|
|
95
|
+
## A.6 Dock Nodes and Drift
|
|
96
|
+
|
|
97
|
+
- A dock node references another node defined in a different file via a link.
|
|
98
|
+
- Pins and connections of the dock node are kept in the importing file.
|
|
99
|
+
- If the external node definition changes, the editor highlights differences (“drift”) between the dock node and its linked definition.
|
|
100
|
+
|
|
101
|
+
## A.7 Buses
|
|
102
|
+
|
|
103
|
+
A bus simplifies routing:
|
|
104
|
+
|
|
105
|
+
- Outputs connected to a bus are forwarded to inputs of the same name.
|
|
106
|
+
- Buses do not introduce new message names; they only group routes.
|
|
107
|
+
|
|
108
|
+
Buses are created in the editor by the user to improve the readability of the vmblu diagram.
|
|
109
|
+
|
|
110
|
+
## A.8 Pads
|
|
111
|
+
|
|
112
|
+
Pads are how group nodes expose their internal pins externally.
|
|
113
|
+
If a connection omits the node in its address, it refers to a pad on the group itself.
|
|
114
|
+
|
|
115
|
+
## A.9 Connections
|
|
116
|
+
|
|
117
|
+
A connection is between pins or interfaces.
|
|
118
|
+
|
|
119
|
+
For a connection between pins, the message flow is from 'src' to 'dst'. So 'src is either an ouput pin of a node or an input pin of the containing group node (a pad in the editor) and 'dst' is either an input pin of a node or an ouput pin of the containing group node.
|
|
120
|
+
|
|
121
|
+
When connecting interfaces the *ouput pins* of the interface are connected the *input pins* with the same name, of the interface on the other node, and in the same way the *input pins* of the interface are connected to the *output pins* with the same name of the interface connected to on the other node. Not all pins have to be present in both interfaces.
|
|
122
|
+
|
|
123
|
+
## A.10 AI Generation Guidelines
|
|
124
|
+
|
|
125
|
+
For LLMs working with vmblu files:
|
|
126
|
+
|
|
127
|
+
- Respect node and pin names — do not rename unless explicitly asked.
|
|
128
|
+
- Connection rules — only connect compatible pins:
|
|
129
|
+
|
|
130
|
+
- output → input
|
|
131
|
+
- request → reply
|
|
132
|
+
|
|
133
|
+
- Reply requirement — every new request pin should be connected to at least one reply pin.
|
|
134
|
+
- Do not edit editor fields — they are for the graphical editor only.
|
|
135
|
+
- When generating source code for the nodes in the vmblu file, only generate code for the nodes, the *main* function and node setup is generated directly from the vmblu file itself.
|
|
136
|
+
- When changing code for an application only change the code for the nodes, not the application file that was generated. That file will be re-generated by the editor.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "@vizual_model/cli/templates/0.8.3/vmblu.schema.json",
|
|
4
|
+
"title": "vmblu Model (v0.8.2 draft)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["header", "root"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"header": { "$ref": "#/$defs/Header" },
|
|
10
|
+
"imports": {
|
|
11
|
+
"description": "List of vmblu files that are referenced in the model. Allows for faster loading.",
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": { "type": "string", "minLength": 1 },
|
|
14
|
+
"uniqueItems": true
|
|
15
|
+
},
|
|
16
|
+
"factories": {
|
|
17
|
+
"description": "List of files where the source code of the source nodes can be found. Used by the docgen tool.",
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": { "type": "string", "minLength": 1 },
|
|
20
|
+
"uniqueItems": true
|
|
21
|
+
},
|
|
22
|
+
"libraries": {
|
|
23
|
+
"description": "List of vmblu files from which the editor allows the user to select nodes in an easy way.",
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": { "type": "string", "minLength": 1 },
|
|
26
|
+
"uniqueItems": true
|
|
27
|
+
},
|
|
28
|
+
"root": {
|
|
29
|
+
"description": "The starting point of the vmblu model. Every field named 'editor' is there only to let the editor display the model for the user.",
|
|
30
|
+
"$ref": "#/$defs/Node"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"$defs": {
|
|
34
|
+
"Header": {
|
|
35
|
+
"description": "Contains meta information about the model. Used by the editor.",
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"version": { "type": "string" },
|
|
39
|
+
"created": { "type": "string", "format": "date-time" },
|
|
40
|
+
"saved": { "type": "string", "format": "date-time" },
|
|
41
|
+
"utc": { "type": "string", "format": "date-time" },
|
|
42
|
+
"style": { "type": "string" },
|
|
43
|
+
"runtime": { "type": "string" }
|
|
44
|
+
},
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"required": ["version", "created", "saved", "utc"]
|
|
47
|
+
},
|
|
48
|
+
"Node" : {
|
|
49
|
+
"description": "A node describes a component of a software system. Nodes communicate via messages. There are three types of nodes.",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"kind": {"enum": ["source", "group", "dock"]},
|
|
53
|
+
"name": { "type": "string", "minLength": 1, "pattern": "^[^@]+$" },
|
|
54
|
+
"label": { "type": "string"},
|
|
55
|
+
"interfaces":{
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "$ref": "#/$defs/Interface" }
|
|
58
|
+
},
|
|
59
|
+
"prompt": {"type": "string"},
|
|
60
|
+
"sx": { "$ref": "#/$defs/NodeInitialization" },
|
|
61
|
+
"rx": { "$ref": "#/$defs/RuntimeDirectives" }
|
|
62
|
+
},
|
|
63
|
+
"oneOf": [
|
|
64
|
+
{ "$ref": "#/$defs/SourceNode" },
|
|
65
|
+
{ "$ref": "#/$defs/GroupNode" },
|
|
66
|
+
{ "$ref": "#/$defs/DockNode" }
|
|
67
|
+
],
|
|
68
|
+
"required": ["kind", "name"],
|
|
69
|
+
"unevaluatedProperties": false
|
|
70
|
+
},
|
|
71
|
+
"SourceNode": {
|
|
72
|
+
"description": "A source node has an implementation in source code.",
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"kind": {"const": "source"},
|
|
76
|
+
"factory": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"path": { "type": "string", "minLength": 1 },
|
|
80
|
+
"function": { "type": "string", "minLength": 1 }
|
|
81
|
+
},
|
|
82
|
+
"required": ["name"]
|
|
83
|
+
},
|
|
84
|
+
"editor": { "$ref": "#/$defs/EditorNode" }
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"GroupNode": {
|
|
88
|
+
"description": "A group node consists of other connected nodes. It allows to build modular models.",
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"kind": {"const": "group"},
|
|
92
|
+
"nodes": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"items": { "$ref": "#/$defs/Node" }
|
|
95
|
+
},
|
|
96
|
+
"connections": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"items": { "$ref": "#/$defs/Connection" }
|
|
99
|
+
},
|
|
100
|
+
"editor": { "$ref": "#/$defs/EditorGroupNode" }
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"DockNode": {
|
|
104
|
+
"description": "A dock node is a placeholder for a node that is defined in another file, specified in the link field.",
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"kind": {"const": "dock"},
|
|
108
|
+
"link": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"required": ["name"],
|
|
111
|
+
"properties": {
|
|
112
|
+
"path": { "type": "string", "minLength": 1 },
|
|
113
|
+
"node": { "type": "string", "minLength": 1, "pattern": "^[^@]+$" }
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"editor": { "$ref": "#/$defs/EditorNode" }
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"Interface": {
|
|
120
|
+
"description": "An interface groups a number of pins of a node.",
|
|
121
|
+
"type": "object",
|
|
122
|
+
"required": ["name"],
|
|
123
|
+
"properties": {
|
|
124
|
+
"name": { "type": "string", "pattern": "^[^@]+$" },
|
|
125
|
+
"pins": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"items": { "$ref": "#/$defs/Pin" }
|
|
128
|
+
},
|
|
129
|
+
"editor" : {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": ["id"],
|
|
132
|
+
"properties": {
|
|
133
|
+
"id": {"type": "integer"}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"Pin": {
|
|
139
|
+
"description": "A node can receive or send a message via a pin. Inputs connect to outputs and requests connect to replies.",
|
|
140
|
+
"type": "object",
|
|
141
|
+
"required": ["name", "kind"],
|
|
142
|
+
"properties": {
|
|
143
|
+
"name": { "type": "string", "minLength": 1, "pattern": "^[^@]+$"},
|
|
144
|
+
"kind": { "enum": ["input", "output", "request", "reply"] },
|
|
145
|
+
"editor": {"$ref": "#/$defs/EditorPin"}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"Connection": {
|
|
149
|
+
"description": "A connection can be made between two pins or between two interfaces. For pins the message flow is from 'src' to 'dst', so 'src' is either an output pin of a node or an input pin of the containing group node and 'dst' is either an input pin of a node or an output pin of the containing group node.",
|
|
150
|
+
"type": "object",
|
|
151
|
+
"additionalProperties": false,
|
|
152
|
+
"required": ["from", "to"],
|
|
153
|
+
"properties": {
|
|
154
|
+
"src": { "$ref": "#/$defs/Address" },
|
|
155
|
+
"dst": { "$ref": "#/$defs/Address" }
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"Address": {
|
|
159
|
+
"description": "The format for the address of a pin or interface. If node is omitted the node is the containing group node, and the pin or interface are represented as pads.",
|
|
160
|
+
"type": "object",
|
|
161
|
+
"additionalProperties": false,
|
|
162
|
+
"properties": {
|
|
163
|
+
"node": { "type": "string", "minLength": 1, "pattern": "^[^@]+$" },
|
|
164
|
+
"pin": { "type": "string", "minLength": 1, "pattern": "^[^@]+$" },
|
|
165
|
+
"interface": { "type": "string", "minLength": 1, "pattern": "^[^@]+$" }
|
|
166
|
+
},
|
|
167
|
+
"oneOf": [
|
|
168
|
+
{ "required": ["pin"] },
|
|
169
|
+
{ "required": ["interface"] }
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
"NodeInitialization": {
|
|
173
|
+
"description": "Node-defined initialization data passed to the node at creation time. Shape is entirely up to the node designer.",
|
|
174
|
+
"type": "object"
|
|
175
|
+
},
|
|
176
|
+
"RuntimeDirectives": {
|
|
177
|
+
"description": "Runtime-defined creation/hosting directives. Shape is determined by the runtime (e.g., host=worker, debug=true).",
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"$contract": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"description": "Optional contract/version tag or URI (e.g., 'vmblu.rx/v1'). Lets tools know which directive dialect to expect."
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"examples": [
|
|
186
|
+
{ "$contract": "vmblu.rx/v1", "host": "worker", "debug": true, "logLevel": "info" }
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
"EditorNode": {
|
|
190
|
+
"description": "Allows the editor to position and draw the node.",
|
|
191
|
+
"type": "object",
|
|
192
|
+
"properties": {
|
|
193
|
+
"rect": { "type": "string" }
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"EditorGroupNode": {
|
|
197
|
+
"description": "Allows the editor to position and draw the content of a group node in a dedicated view.",
|
|
198
|
+
"type": "object",
|
|
199
|
+
"properties": {
|
|
200
|
+
"rect": { "type": "string" },
|
|
201
|
+
"view": {
|
|
202
|
+
"type": "object",
|
|
203
|
+
"required": ["rect"],
|
|
204
|
+
"properties": {
|
|
205
|
+
"state": {"enum": ["open", "closed"]},
|
|
206
|
+
"rect": { "type": "string" },
|
|
207
|
+
"transform": { "type": "string" }
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"buses": {
|
|
211
|
+
"type": "array",
|
|
212
|
+
"items": {"$ref": "#/$defs/EditorBus"}
|
|
213
|
+
},
|
|
214
|
+
"routes": {
|
|
215
|
+
"type": "array",
|
|
216
|
+
"items": {"$ref": "#/$defs/EditorRoute"}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"EditorPin" : {
|
|
221
|
+
"description": "Allows the editor to draw the pin in the node it belongs to.",
|
|
222
|
+
"type": "object",
|
|
223
|
+
"properties": {
|
|
224
|
+
"id": { "type": "integer" },
|
|
225
|
+
"align": { "enum": ["left", "right"]},
|
|
226
|
+
"pad": {
|
|
227
|
+
"type": "object",
|
|
228
|
+
"required": ["rect"],
|
|
229
|
+
"properties": {
|
|
230
|
+
"rect": { "type": "string" },
|
|
231
|
+
"align": { "enum": ["left", "right"]}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"EditorBus": {
|
|
237
|
+
"description": "A bus is an easy way to route multiple connections between nodes. A bus can also do some routing via a filter.",
|
|
238
|
+
"type": "object",
|
|
239
|
+
"required": ["kind","name"],
|
|
240
|
+
"properties": {
|
|
241
|
+
"kind": { "enum": ["busbar", "cable"]},
|
|
242
|
+
"name": { "type": "string", "minLength": 1, "pattern": "^[^@]+$" },
|
|
243
|
+
"filter": { "$ref": "#/$defs/Filter" },
|
|
244
|
+
"start" : { "type": "string"},
|
|
245
|
+
"wire": { "type": "string" }
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"Filter": {
|
|
249
|
+
"description": "A bus uses a filter to route messages based on message parameters. A filter is a software routine.",
|
|
250
|
+
"type": "object",
|
|
251
|
+
"required": ["path", "function"],
|
|
252
|
+
"properties": {
|
|
253
|
+
"path": { "type": "string", "minLength": 1 },
|
|
254
|
+
"function": { "type": "string", "minLength": 1 }
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"EditorRoute": {
|
|
258
|
+
"description": "A route is how the editor shows the connections between pins or interfaces.The from-string and to-string start with either pin, bus, pad or itf",
|
|
259
|
+
"type": "object",
|
|
260
|
+
"properties": {
|
|
261
|
+
"from":{ "type": "string" },
|
|
262
|
+
"to":{ "type": "string" },
|
|
263
|
+
"wire": { "type": "string" }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|