Module anatomy
A module is a plain Node.js project with two files the platform requires: module.json (identity in the registry) and index.js (entry point in the container). The example module has the minimal layout:
acme-hello/
├── module.json # required: code, version, module dependencies
├── package.json # npm dependencies, "main": "index.js"
├── .npmrc # optional: @orbito scope line, if not in ~/.npmrc
├── index.js # required: entry point, registers everything
└── pages/
└── hello.yaml # OVE view registered with Pages.registerPages
omm init writes these files plus a .gitignore.
Larger modules add src/ (business logic), methods/ (one file per method, loaded with Utils.loadMethods), test/ and README.md. A README.md inside the package becomes the version description in the registry.
module.json
The manifest is the only source of the module's identity. omm validates it before upload and the registry validates it again on upload and on release.
{
"name": "acme/hello",
"version": "0.1.0",
"dependsOn": ["core/orders"],
"singleton": true,
"secrets": [
{ "name": "ACME_API_KEY", "description": "Application key of the Acme API" }
]
}
| Field | Required | Rules |
|---|---|---|
name | yes | Full module code namespace/name, matching ^[a-z0-9-]+/[a-z0-9_-]+$ (no dots). It must be identical to the code of the module card in the registry. omm init <name> creates the card and writes its code here: namespace is the namespace the card belongs to (omm whoami lists yours) and name is the slug of the card's display name. omm init --code <namespace/name> uses an existing card. |
version | yes | Semver without build metadata: 1.2.3 or 1.2.3-dev.1 (+build is rejected, the version becomes a Docker tag). |
dependsOn | no | Array of Orbito module codes (namespace/name) this module needs. omm install installs missing dependencies too. Not npm dependencies, those go to package.json. |
singleton | no | Boolean, default true: one instance per company. Set false when a company may install the module several times (one per shop, one per carrier account); each install then needs a unique instance name. The flag changes nothing in your code. |
secrets | no | Application keys of the module author, declared without values: [{ name, description? }]. name matches ^[A-Z][A-Z0-9_]*$, max 64 chars, no ORBITO_ prefix, no duplicates; description max 200 chars. Values are set with omm secrets set NAME and reach the container as process.env.NAME. Per-company settings are not secrets, use config blocks for them. |
minOrbitoVersion | no | Semver of the minimal core version. |
files | no | Array of paths to pack. Without it omm packs the whole directory minus its exclusions. |
Details in Polish: Module structure.
index.js
The container runs node index.js in the module directory (image node:20-alpine). The file registers everything at top level:
Core.registerMethodfor every method the front end, automations or other modules call,Core.registerHookfor every hook the module answers,Pages.registerPagesfor OVE views,Config.registerConfigBlocksfor settings,Core.onEnabled/Core.onDisabledfor lifecycle events.
Requiring @orbito/api-lib opens the connection to the core. Calls made before the connection is ready are queued, and registrations are re-sent automatically after a reconnect, so top-level registration is all you need. Point to files relative to the module, for example path.join(__dirname, 'pages', 'hello.yaml').
Modules and the SDK are CommonJS: use require / module.exports, 'use strict'; at the top, and no "type": "module" in package.json.
The full example is acme/hello.
package.json
{
"name": "acme-hello",
"version": "0.1.0",
"private": true,
"main": "index.js",
"dependencies": {
"@orbito/api-lib": "^4.3.0",
"fastest-validator": "^1.19.1"
}
}
mainmust beindex.js.- The registry build runs
npm install --only=production, so everything the module requires at runtime belongs independencies. Commitpackage-lock.jsonfor reproducible builds. node_modules/is never packed; the build installs dependencies itself.@orbito/api-libis not on npmjs.org: it lives in the Orbito npm registry.npm installneeds the scope line@orbito:registry=https://git.kozminski.net.pl/api/packages/orbito/npm/, either in~/.npmrc(npm config set @orbito:registry https://git.kozminski.net.pl/api/packages/orbito/npm/, once per machine) or in a.npmrcin the module directory. The line holds no token, so it is safe in the module directory, butommdoes not pack.npmrcby default and the registry build uses its own.npmrcanyway.- Keep
versionequal tomodule.json. Onlymodule.jsonis checked, but a drift confuses humans.
Versioning
module.jsonversionis the version of the package. You change it by hand;ommnever does.- A new version must be higher than the last released version of the module.
- Publishing the same version again overwrites a draft (useful when a build failed). A released version is immutable.
- Drafts do not need a new version.
omm publishoverwrites the draft of the same version, andomm updateon the version the instance already runs reloads it with the fresh draft image. Bump the version beforeomm publish --release(and before the first draft after a release), or when you want to keep the previous draft untouched.
What omm publish checks
Locally, before anything is sent:
module.jsonexists and is valid JSON.name,version,dependsOn,filesandsecretsfollow the rules above.- The package is built without
node_modules/,.git/,.env*,.npmrc,*.zip,.gitignore,.dockerignore,CLAUDE.mdand editor directories. Preview it withomm pack --dry-run.
In the registry:
- A module card with exactly this
nameexists and the logged-in account is a member of its namespace.omm initcreates it;omm publishnever does. - The manifest is valid for that card.
- The version is higher than the last released one and is not an already released version.
- The Docker image builds.
omm publishwaits for the build (default 600 s,--timeout) and prints the build error when it fails. - With
--releaseonly: the draft becomes published, the previously published version becomes deprecated, and every declared secret without a value is printed as a warning.
