Skip to main content

Internal Module Registration

Some modules make use of internal submodules that may well require their own configuration. Exporting all these to the top level of the application would add unnecessary direct dependencies, so modules can provide a list of modules to register when they are themselves loaded.

For example, this code might be in one file...

output.js
import { ModuleManager, Schema } from '@versionzero/module-manager';

class Formatter {
static moduleProvides = "formatter"
format(s) { return s }
}
class UpperCase extends Formatter {
format(s) {
return s.toUpperCase();
}
}
class EndPadder extends Formatter {
static moduleInfo = {
name: 'pad',
schema: new Schema('object')
.property('length', new Schema('number').default(50).validator('$positive'))
.property('char', new Schema('string').default(' '))
}
format(s) {
return s.padEnd(this.length, this.char);
}
}
class Replacer extends Formatter {
static moduleSchema = new Schema('object')
.property('pattern', new Schema('string').default('x'))
.property('replacement', new Schema('string').default('_'))

format(s) {
return s.replaceAll(this.pattern, this.replacement);
}
}

export class Output {
static moduleInfo = {
schema: new Schema('object')
.property('formatter', new Schema('formatter')),
definitions: [Replacer, { name: 'upper', ModuleClass: UpperCase }, EndPadder]

// see next section...
//definitions: [{ModuleClass: Replacer, relative: true}, { name: 'upper', ModuleClass: UpperCase, relative: true }, {ModuleClass: EndPadder, relative: true}]
}

write(s) {
const line = (this.formatter? this.formatter.format(s) : s);
console.log(line);
}
}

but the application code, in a different file, doesn't need to have any knowledge of the internal modules;

myapp.js
//import { ModuleManager, Schema } from '@versionzero/module-manager';
//import { Output } from './output.js'

class MyApp {
static moduleDefinitions = [Output]
static moduleSchema = new Schema('object')
.property('output', new Schema('Output').meta('hidden'))
.property('message', new Schema('string').default('hello, world'))

async main() {
this.output.write(this.message);
}
}

await new ModuleManager()
.register(MyApp)
//.run({argv: ['--help']})
.run({argv: ['--of=replacer', '--rp=o', '--rr=0']})
//.run({argv: ['--of=replacer', '--rp=o', '--rr=0', '--dump=-']})
//.run({argv: ['--output-formatter', 'pad', '--pad-char', '_']})

// see next section
//.run({argv: ['--of=replacer', '--orp=o', '--orr=0', '--dump=-']})

Relative Module Definitions

The above code prints the following when run:

% node myapp.js --help
Usage: MyApp [options]
--config (-C) [path|-] - load configuration from file (or -
for stdin)
--help (-h) [advanced] - display help information
--message (-m) [string] - (default:"hello, world")
--output-formatter (--of) ["Pad"|"Replacer"|"Upper"]

--pad-char (--pc) [string] - (default:" ")
--pad-length (--pl) [positive] - (default:«50»)
--replacer-pattern (--rp) [string] - (default:"x")
--replacer-replacement (--rr) [string]
- (default:"_")

% node myapp.js --of replacer --rp o --rr 0
hell0, w0rld

% node myapp.js --of replacer --rp o --rr 0 --dump -
{
"myApp": {
"message": "hello, world",
"output": "Output"
},
"output": {
"formatter": "Replacer"
},
"replacer": {
"pattern": "o",
"replacement": "0"
}
}

Notice how the replacer configuration is at the root of the configuration. If we wanted to keep this configuration more tightly coupled with the "parent" Output module, we could add the "relative" option to the moduleInfo.definitions entries (see the commented-out version).

Observe how the replacer values are now nested (as seen in both --help and --dump configuration.)

% node myapp.js --help
Usage: MyApp [options]
--config (-C) [path|-] - load configuration from file (or -
for stdin)
--help (-h) [advanced] - display help information
--message (-m) [string] - (default:"hello, world")
--output-formatter (--of) ["Pad"|"Replacer"|"Upper"]

--output-pad-char (--opc) [string] - (default:" ")
--output-pad-length (--opl) [positive]
- (default:«50»)
--output-replacer-pattern (--orp) [string]
- (default:"x")
--output-replacer-replacement (--orr) [string]
- (default:"_")

% node myapp.js --of replacer --orp=o --orr=0 --dump=-
{
"myApp": {
"message": "hello, world",
"output": "Output"
},
"output": {
"formatter": "Replacer",
"replacer": {
"pattern": "o",
"replacement": "0"
}
}
}
caution

It is very easy to accidentally create a naming conflict between a relative module definition and the regular internal properties of a module! For example, if Output were made relative to MyApp, it would clash with the existing output property that holds the Output instance.