@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 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
- 0.8.2/
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, so users can run vmblu migrate ….
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
 
@@ -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: 0.8.2)' },
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 || '0.8.2';
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: "0.8.2",
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: "0.0.0",
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 = "0.8.2",
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: "^0.1.0" }, ui);
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) + '-app.js';
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) return
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 && (widget.wid == 0)) widget.wid = this.generateWid();
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
- const SRC_DOC_VERSION = '0.2';
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: SRC_DOC_VERSION,
23425
+ version: PROFILE_VERSION,
23417
23426
  generatedAt,
23418
23427
  entries: rxtx
23419
23428
  };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@vizualmodel/vmblu-cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
+ "schemaVersion": "0.8.3",
4
5
  "type": "module",
5
6
  "bin": {
6
7
  "vmblu": "bin/vmblu.js"
@@ -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
+