@node-i3x/app 0.1.0

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 ADDED
@@ -0,0 +1,139 @@
1
+ # @node-i3x/app
2
+
3
+ > **Expose any OPC UA server as an i3X REST API -- one command.**
4
+
5
+ [![npm](https://img.shields.io/npm/v/@node-i3x/app)](https://www.npmjs.com/package/@node-i3x/app)
6
+ [![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ESM--only-blue)](https://www.typescriptlang.org)
8
+ [![License: AGPL-3.0-or-later OR Commercial](https://img.shields.io/badge/license-AGPL--3.0--or--later%20OR%20Commercial-orange)](../../LICENSE)
9
+ [![Built by Sterfive](https://img.shields.io/badge/built%20by-Sterfive-ff6600)](https://sterfive.com)
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npx @node-i3x/app -e opc.tcp://my-plc:4840
15
+ ```
16
+
17
+ That's it. The i3X REST API is now available at `http://localhost:8080`.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install -g @node-i3x/app
23
+ ```
24
+
25
+ Or use directly with `npx` (no install needed).
26
+
27
+ ## CLI Options
28
+
29
+ ```
30
+ Usage: i3x [options]
31
+
32
+ Expose OPC UA servers as i3X REST APIs
33
+
34
+ Options:
35
+ -V, --version Show version
36
+ -e, --endpoint <url> OPC UA endpoint URL
37
+ -p, --port <port> REST API port (default: 8080)
38
+ -H, --host <host> REST API bind address (default: 0.0.0.0)
39
+ --security-mode <mode> OPC UA security mode (default: None)
40
+ --optimized-client <mode> Optimized client: auto | disabled
41
+ --subscription-interval <seconds> Subscription interval in seconds
42
+ --log-level <level> debug | info | warn | error
43
+ --no-model-preload Skip model preload on startup
44
+ -c, --config <path> Path to config file
45
+ -h, --help Show help
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ i3X uses layered configuration. Each layer overrides the previous:
51
+
52
+ ```
53
+ 1. Built-in defaults
54
+ 2. Config file (i3x.config.yml)
55
+ 3. Environment variables (I3X_*)
56
+ 4. CLI arguments (--endpoint, --port, ...)
57
+ ```
58
+
59
+ ### Config File
60
+
61
+ Create an `i3x.config.yml` in your project root:
62
+
63
+ ```yaml
64
+ # i3x.config.yml
65
+ endpoint: opc.tcp://192.168.1.100:4840
66
+ port: 8080
67
+ host: 0.0.0.0
68
+ logLevel: info
69
+ subscriptionInterval: 5
70
+ modelPreload: true
71
+ ```
72
+
73
+ Supported file names (auto-discovered):
74
+
75
+ | File | Format |
76
+ |---|---|
77
+ | `i3x.config.yml` | YAML |
78
+ | `i3x.config.yaml` | YAML |
79
+ | `i3x.config.json` | JSON |
80
+ | `.i3xrc` | JSON |
81
+ | `.i3xrc.json` | JSON |
82
+ | `.i3xrc.yml` | YAML |
83
+ | `package.json` (`"i3x"` key) | JSON |
84
+
85
+ ### Environment Variables
86
+
87
+ All environment variables are prefixed with `I3X_`:
88
+
89
+ | Variable | Config key | Default |
90
+ |---|---|---|
91
+ | `I3X_OPCUA_ENDPOINT` | `endpoint` | `opc.tcp://localhost:4840` |
92
+ | `I3X_PORT` | `port` | `8080` |
93
+ | `I3X_HOST` | `host` | `0.0.0.0` |
94
+ | `I3X_OPCUA_SECURITY_MODE` | `securityMode` | `None` |
95
+ | `I3X_OPCUA_OPTIMIZED_CLIENT` | `optimizedClient` | `auto` |
96
+ | `I3X_SUBSCRIPTION_INTERVAL_SECONDS` | `subscriptionInterval` | `5` |
97
+ | `I3X_LOG_LEVEL` | `logLevel` | `info` |
98
+ | `I3X_MODEL_PRELOAD_ON_STARTUP` | `modelPreload` | `true` |
99
+
100
+ ## Programmatic Usage
101
+
102
+ ```ts
103
+ import { resolveConfig, startServer } from '@node-i3x/app';
104
+
105
+ const config = await resolveConfig({
106
+ endpoint: 'opc.tcp://my-plc:4840',
107
+ port: 9090,
108
+ });
109
+
110
+ await startServer(config, '1.0.0');
111
+ ```
112
+
113
+ ## How It Works
114
+
115
+ ```
116
+ Your OPC UA Server i3X (@node-i3x/app)
117
+ +-----------------+ +---------------------------+
118
+ | AddressSpace | <-----> | OpcUaDataSourceAdapter |
119
+ | (PLCs, sensors) | opc.tcp | ModelService |
120
+ +-----------------+ | ValueService |
121
+ | HistoryService |
122
+ | SubscriptionService |
123
+ | |
124
+ | i3X REST API (Fastify) |
125
+ +----------+----------------+
126
+ |
127
+ http://0.0.0.0:8080
128
+ |
129
+ +----------v----------------+
130
+ | Your Web App / Dashboard |
131
+ +---------------------------+
132
+ ```
133
+
134
+ ## License
135
+
136
+ **AGPL-3.0-or-later** -- Free for open-source projects.
137
+
138
+ For commercial / proprietary use, contact [Sterfive](https://sterfive.com)
139
+ for a commercial license.
package/dist/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as R}from"module";import{Command as U}from"commander";import{cosmiconfig as h}from"cosmiconfig";import{load as u}from"js-yaml";var O={endpoint:"opc.tcp://localhost:4840",port:8080,host:"0.0.0.0",securityMode:"None",optimizedClient:"auto",subscriptionInterval:5,logLevel:"info",modelPreload:!0,failOnPreloadError:!1};function d(o){return process.env[o]}function g(o){let e=process.env[o];return e?parseInt(e,10):void 0}function v(o){let e=process.env[o];if(e)return e==="1"||e.toLowerCase()==="true"}function y(){let o={},e=d("I3X_OPCUA_ENDPOINT")??d("I3X_ENDPOINT");e&&(o.endpoint=e);let n=g("I3X_PORT");n!==void 0&&(o.port=n);let t=d("I3X_HOST");t&&(o.host=t);let r=d("I3X_OPCUA_SECURITY_MODE");r&&(o.securityMode=r);let i=d("I3X_OPCUA_OPTIMIZED_CLIENT");(i==="auto"||i==="disabled")&&(o.optimizedClient=i);let s=g("I3X_SUBSCRIPTION_INTERVAL_SECONDS");s!==void 0&&(o.subscriptionInterval=s);let c=d("I3X_LOG_LEVEL");c&&(o.logLevel=c);let l=v("I3X_MODEL_PRELOAD_ON_STARTUP");l!==void 0&&(o.modelPreload=l);let a=v("I3X_FAIL_STARTUP_ON_MODEL_PRELOAD_ERROR");return a!==void 0&&(o.failOnPreloadError=a),o}async function I(o,e){let n={},t=h("i3x",{searchPlaces:["i3x.config.yml","i3x.config.yaml","i3x.config.json",".i3xrc",".i3xrc.json",".i3xrc.yml",".i3xrc.yaml","package.json"],loaders:{".yml":(i,s)=>u(s),".yaml":(i,s)=>u(s)}});try{let i=e?await t.load(e):await t.search();i&&!i.isEmpty&&(n=i.config)}catch{}let r=y();return{...O,...n,...r,...o}}import{consoleLogger as E,HistoryService as _,ModelService as b,SubscriptionService as L,ValueService as w}from"@node-i3x/core";import{OpcUaClient as A,OpcUaDataSourceAdapter as T}from"@node-i3x/opcua-connector";import{createApp as M}from"@node-i3x/rest-server";function C(o,e,n){let t=[""," i3X Server v"+o,""," OPC UA: "+e.endpoint," REST: http://"+e.host+":"+e.port];n!==void 0&&t.push(" Model: "+n+" nodes"),t.push(""),t.push(" Press Ctrl+C to stop"),t.push("");let i=Math.max(...t.map(a=>a.length))+2,s="-".repeat(i),c=" +"+s+"+",l=" +"+s+"+";console.log(c);for(let a of t)console.log(" |"+a.padEnd(i)+"|");console.log(l),console.log()}async function P(o,e){let n=E,t=new A({endpointUrl:o.endpoint,securityMode:o.securityMode,optimizedClient:o.optimizedClient},n),r=new T(t,n),i=new b(r,n),s=new w(r,i,n),c=new _(r,i,n),l=new L(r,i,n,o.subscriptionInterval),a=await M({dataSource:r,modelService:i,valueService:s,historyService:c,subscriptionService:l,logger:n});await r.connect();let p;if(o.modelPreload)try{p=(await i.preloadModel()).nodesById.size}catch(m){n.error("Model preload failed: "+String(m)),o.failOnPreloadError&&process.exit(1)}await a.listen({port:o.port,host:o.host}),C(e,o,p);let f=async()=>{n.info("Shutting down..."),await a.close(),await l.close(),await r.disconnect(),process.exit(0)};process.on("SIGINT",f),process.on("SIGTERM",f)}var z=R(import.meta.url),x=z("../package.json"),S=new U;S.name("i3x").description("Expose OPC UA servers as i3X REST APIs").version(x.version).option("-e, --endpoint <url>","OPC UA endpoint URL").option("-p, --port <port>","REST API port",parseInt).option("-H, --host <host>","REST API bind address").option("--security-mode <mode>","OPC UA security mode").option("--optimized-client <mode>","Optimized client: auto | disabled").option("--subscription-interval <seconds>","Subscription interval in seconds",parseInt).option("--log-level <level>","Log level: debug | info | warn | error").option("--no-model-preload","Skip model preload on startup").option("-c, --config <path>","Path to config file").action(async o=>{let e={};o.endpoint&&(e.endpoint=o.endpoint),o.port!==void 0&&(e.port=o.port),o.host&&(e.host=o.host),o.securityMode&&(e.securityMode=o.securityMode),o.optimizedClient&&(e.optimizedClient=o.optimizedClient),o.subscriptionInterval!==void 0&&(e.subscriptionInterval=o.subscriptionInterval),o.logLevel&&(e.logLevel=o.logLevel),o.modelPreload===!1&&(e.modelPreload=!1);let n=await I(e,o.config);await P(n,x.version)});S.parse();
3
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/server.ts","../src/banner.ts"],"mappings":"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","names":["createRequire","Command","cosmiconfig","loadYaml","DEFAULTS","envStr","key","envInt","v","envBool","fromEnv","result","endpoint","port","host","securityMode","optimizedClient","interval","logLevel","preload","failOnPreload","resolveConfig","cliArgs","configPath","fileConfig","explorer","_filepath","content","envConfig","consoleLogger","HistoryService","ModelService","SubscriptionService","ValueService","OpcUaClient","OpcUaDataSourceAdapter","createApp","printBanner","version","config","nodeCount","lines","width","l","hr","top","bot","line","startServer","config","version","logger","consoleLogger","opcuaClient","OpcUaClient","dataSource","OpcUaDataSourceAdapter","modelService","ModelService","valueService","ValueService","historyService","HistoryService","subscriptionService","SubscriptionService","app","createApp","nodeCount","err","printBanner","shutdown","require","createRequire","pkg","program","Command","opts","cliArgs","config","resolveConfig","startServer"]}
@@ -0,0 +1,16 @@
1
+ interface I3xConfig {
2
+ endpoint: string;
3
+ port: number;
4
+ host: string;
5
+ securityMode: string;
6
+ optimizedClient: 'auto' | 'disabled';
7
+ subscriptionInterval: number;
8
+ logLevel: string;
9
+ modelPreload: boolean;
10
+ failOnPreloadError: boolean;
11
+ }
12
+ declare function resolveConfig(cliArgs: Partial<I3xConfig>, configPath?: string): Promise<I3xConfig>;
13
+
14
+ declare function startServer(config: I3xConfig, version: string): Promise<void>;
15
+
16
+ export { type I3xConfig, resolveConfig, startServer };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{cosmiconfig as C}from"cosmiconfig";import{load as m}from"js-yaml";var x={endpoint:"opc.tcp://localhost:4840",port:8080,host:"0.0.0.0",securityMode:"None",optimizedClient:"auto",subscriptionInterval:5,logLevel:"info",modelPreload:!0,failOnPreloadError:!1};function d(o){return process.env[o]}function g(o){let e=process.env[o];return e?parseInt(e,10):void 0}function I(o){let e=process.env[o];if(e)return e==="1"||e.toLowerCase()==="true"}function P(){let o={},e=d("I3X_OPCUA_ENDPOINT")??d("I3X_ENDPOINT");e&&(o.endpoint=e);let n=g("I3X_PORT");n!==void 0&&(o.port=n);let r=d("I3X_HOST");r&&(o.host=r);let i=d("I3X_OPCUA_SECURITY_MODE");i&&(o.securityMode=i);let t=d("I3X_OPCUA_OPTIMIZED_CLIENT");(t==="auto"||t==="disabled")&&(o.optimizedClient=t);let s=g("I3X_SUBSCRIPTION_INTERVAL_SECONDS");s!==void 0&&(o.subscriptionInterval=s);let c=d("I3X_LOG_LEVEL");c&&(o.logLevel=c);let l=I("I3X_MODEL_PRELOAD_ON_STARTUP");l!==void 0&&(o.modelPreload=l);let a=I("I3X_FAIL_STARTUP_ON_MODEL_PRELOAD_ERROR");return a!==void 0&&(o.failOnPreloadError=a),o}async function S(o,e){let n={},r=C("i3x",{searchPlaces:["i3x.config.yml","i3x.config.yaml","i3x.config.json",".i3xrc",".i3xrc.json",".i3xrc.yml",".i3xrc.yaml","package.json"],loaders:{".yml":(t,s)=>m(s),".yaml":(t,s)=>m(s)}});try{let t=e?await r.load(e):await r.search();t&&!t.isEmpty&&(n=t.config)}catch{}let i=P();return{...x,...n,...i,...o}}import{consoleLogger as _,HistoryService as O,ModelService as h,SubscriptionService as E,ValueService as y}from"@node-i3x/core";import{OpcUaClient as w,OpcUaDataSourceAdapter as L}from"@node-i3x/opcua-connector";import{createApp as b}from"@node-i3x/rest-server";function v(o,e,n){let r=[""," i3X Server v"+o,""," OPC UA: "+e.endpoint," REST: http://"+e.host+":"+e.port];n!==void 0&&r.push(" Model: "+n+" nodes"),r.push(""),r.push(" Press Ctrl+C to stop"),r.push("");let t=Math.max(...r.map(a=>a.length))+2,s="-".repeat(t),c=" +"+s+"+",l=" +"+s+"+";console.log(c);for(let a of r)console.log(" |"+a.padEnd(t)+"|");console.log(l),console.log()}async function T(o,e){let n=_,r=new w({endpointUrl:o.endpoint,securityMode:o.securityMode,optimizedClient:o.optimizedClient},n),i=new L(r,n),t=new h(i,n),s=new y(i,t,n),c=new O(i,t,n),l=new E(i,t,n,o.subscriptionInterval),a=await b({dataSource:i,modelService:t,valueService:s,historyService:c,subscriptionService:l,logger:n});await i.connect();let p;if(o.modelPreload)try{p=(await t.preloadModel()).nodesById.size}catch(u){n.error("Model preload failed: "+String(u)),o.failOnPreloadError&&process.exit(1)}await a.listen({port:o.port,host:o.host}),v(e,o,p);let f=async()=>{n.info("Shutting down..."),await a.close(),await l.close(),await i.disconnect(),process.exit(0)};process.on("SIGINT",f),process.on("SIGTERM",f)}export{S as resolveConfig,T as startServer};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/server.ts","../src/banner.ts"],"mappings":"AAAA,OAAS,eAAAA,MAAmB,cAC5B,OAAS,QAAQC,MAAgB,UAcjC,IAAMC,EAAsB,CAC1B,SAAU,2BACV,KAAM,KACN,KAAM,UACN,aAAc,OACd,gBAAiB,OACjB,qBAAsB,EACtB,SAAU,OACV,aAAc,GACd,mBAAoB,EACtB,EAGA,SAASC,EAAOC,EAAiC,CAC/C,OAAO,QAAQ,IAAIA,CAAG,CACxB,CACA,SAASC,EAAOD,EAAiC,CAC/C,IAAME,EAAI,QAAQ,IAAIF,CAAG,EACzB,OAAOE,EAAI,SAASA,EAAG,EAAE,EAAI,MAC/B,CACA,SAASC,EAAQH,EAAkC,CACjD,IAAME,EAAI,QAAQ,IAAIF,CAAG,EACzB,GAAKE,EACL,OAAOA,IAAM,KAAOA,EAAE,YAAY,IAAM,MAC1C,CAEA,SAASE,GAA8B,CACrC,IAAMC,EAA6B,CAAC,EAC9BC,EAAWP,EAAO,oBAAoB,GAAKA,EAAO,cAAc,EAClEO,IAAUD,EAAO,SAAWC,GAEhC,IAAMC,EAAON,EAAO,UAAU,EAC1BM,IAAS,SAAWF,EAAO,KAAOE,GAEtC,IAAMC,EAAOT,EAAO,UAAU,EAC1BS,IAAMH,EAAO,KAAOG,GAExB,IAAMC,EAAeV,EAAO,yBAAyB,EACjDU,IAAcJ,EAAO,aAAeI,GAExC,IAAMC,EAAkBX,EAAO,4BAA4B,GACvDW,IAAoB,QAAUA,IAAoB,cACpDL,EAAO,gBAAkBK,GAE3B,IAAMC,EAAWV,EAAO,mCAAmC,EACvDU,IAAa,SAAWN,EAAO,qBAAuBM,GAE1D,IAAMC,EAAWb,EAAO,eAAe,EACnCa,IAAUP,EAAO,SAAWO,GAEhC,IAAMC,EAAUV,EAAQ,8BAA8B,EAClDU,IAAY,SAAWR,EAAO,aAAeQ,GAEjD,IAAMC,EAAgBX,EAAQ,yCAAyC,EACvE,OAAIW,IAAkB,SAAWT,EAAO,mBAAqBS,GAEtDT,CACT,CAGA,eAAsBU,EACpBC,EACAC,EACoB,CAEpB,IAAIC,EAAiC,CAAC,EAChCC,EAAWvB,EAAY,MAAO,CAClC,aAAc,CACZ,iBACA,kBACA,kBACA,SACA,cACA,aACA,cACA,cACF,EACA,QAAS,CACP,OAAQ,CAACwB,EAAmBC,IAAoBxB,EAASwB,CAAO,EAChE,QAAS,CAACD,EAAmBC,IAAoBxB,EAASwB,CAAO,CACnE,CACF,CAAC,EAED,GAAI,CACF,IAAMhB,EAASY,EACX,MAAME,EAAS,KAAKF,CAAU,EAC9B,MAAME,EAAS,OAAO,EACtBd,GAAU,CAACA,EAAO,UACpBa,EAAab,EAAO,OAExB,MAAQ,CAER,CAGA,IAAMiB,EAAYlB,EAAQ,EAE1B,MAAO,CACL,GAAGN,EACH,GAAGoB,EACH,GAAGI,EACH,GAAGN,CACL,CACF,CCtHA,OACE,iBAAAO,EACA,kBAAAC,EACA,gBAAAC,EACA,uBAAAC,EACA,gBAAAC,MACK,iBACP,OAAS,eAAAC,EAAa,0BAAAC,MAA8B,4BACpD,OAAS,aAAAC,MAAiB,wBCNnB,SAASC,EACdC,EACAC,EACAC,EACM,CACN,IAAMC,EAAQ,CACZ,GACA,iBAAmBH,EACnB,GACA,cAAgBC,EAAO,SACvB,qBAAuBA,EAAO,KAAO,IAAMA,EAAO,IACpD,EAEIC,IAAc,QAChBC,EAAM,KAAK,cAAgBD,EAAY,QAAQ,EAGjDC,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,wBAAwB,EACnCA,EAAM,KAAK,EAAE,EAIb,IAAMC,EADS,KAAK,IAAI,GAAGD,EAAM,IAAKE,GAAMA,EAAE,MAAM,CAAC,EAC9B,EAEjBC,EAAK,IAAI,OAAOF,CAAK,EACrBG,EAAM,MAAQD,EAAK,IACnBE,EAAM,MAAQF,EAAK,IAEzB,QAAQ,IAAIC,CAAG,EACf,QAAWE,KAAQN,EACjB,QAAQ,IAAI,MAAQM,EAAK,OAAOL,CAAK,EAAI,GAAG,EAE9C,QAAQ,IAAII,CAAG,EACf,QAAQ,IAAI,CACd,CDzBA,eAAsBE,EACpBC,EACAC,EACe,CACf,IAAMC,EAASC,EAGTC,EAAc,IAAIC,EACtB,CACE,YAAaL,EAAO,SACpB,aAAcA,EAAO,aACrB,gBAAiBA,EAAO,eAC1B,EACAE,CACF,EACMI,EAAa,IAAIC,EAAuBH,EAAaF,CAAM,EAG3DM,EAAe,IAAIC,EAAaH,EAAYJ,CAAM,EAClDQ,EAAe,IAAIC,EAAaL,EAAYE,EAAcN,CAAM,EAChEU,EAAiB,IAAIC,EAAeP,EAAYE,EAAcN,CAAM,EACpEY,EAAsB,IAAIC,EAC9BT,EACAE,EACAN,EACAF,EAAO,oBACT,EAGMgB,EAAM,MAAMC,EAAU,CAC1B,WAAAX,EACA,aAAAE,EACA,aAAAE,EACA,eAAAE,EACA,oBAAAE,EACA,OAAAZ,CACF,CAAC,EAGD,MAAMI,EAAW,QAAQ,EAGzB,IAAIY,EACJ,GAAIlB,EAAO,aACT,GAAI,CAEFkB,GADc,MAAMV,EAAa,aAAa,GAC5B,UAAU,IAC9B,OAASW,EAAK,CACZjB,EAAO,MAAM,yBAA2B,OAAOiB,CAAG,CAAC,EAC/CnB,EAAO,oBAAoB,QAAQ,KAAK,CAAC,CAC/C,CAIF,MAAMgB,EAAI,OAAO,CAAE,KAAMhB,EAAO,KAAM,KAAMA,EAAO,IAAK,CAAC,EAGzDoB,EAAYnB,EAASD,EAAQkB,CAAS,EAGtC,IAAMG,EAAW,SAAY,CAC3BnB,EAAO,KAAK,kBAAkB,EAC9B,MAAMc,EAAI,MAAM,EAChB,MAAMF,EAAoB,MAAM,EAChC,MAAMR,EAAW,WAAW,EAC5B,QAAQ,KAAK,CAAC,CAChB,EACA,QAAQ,GAAG,SAAUe,CAAQ,EAC7B,QAAQ,GAAG,UAAWA,CAAQ,CAChC","names":["cosmiconfig","loadYaml","DEFAULTS","envStr","key","envInt","v","envBool","fromEnv","result","endpoint","port","host","securityMode","optimizedClient","interval","logLevel","preload","failOnPreload","resolveConfig","cliArgs","configPath","fileConfig","explorer","_filepath","content","envConfig","consoleLogger","HistoryService","ModelService","SubscriptionService","ValueService","OpcUaClient","OpcUaDataSourceAdapter","createApp","printBanner","version","config","nodeCount","lines","width","l","hr","top","bot","line","startServer","config","version","logger","consoleLogger","opcuaClient","OpcUaClient","dataSource","OpcUaDataSourceAdapter","modelService","ModelService","valueService","ValueService","historyService","HistoryService","subscriptionService","SubscriptionService","app","createApp","nodeCount","err","printBanner","shutdown"]}
@@ -0,0 +1,21 @@
1
+ # i3X configuration
2
+ # See: npx @node-i3x/app --help
3
+
4
+ # OPC UA server to connect to
5
+ endpoint: opc.tcp://localhost:4840
6
+
7
+ # REST API settings
8
+ port: 8080
9
+ host: 0.0.0.0
10
+
11
+ # OPC UA security
12
+ securityMode: None
13
+ # optimizedClient: auto
14
+
15
+ # Domain settings
16
+ subscriptionInterval: 5
17
+ modelPreload: true
18
+ # failOnPreloadError: false
19
+
20
+ # Logging
21
+ logLevel: info
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@node-i3x/app",
3
+ "version": "0.1.0",
4
+ "description": "i3X CLI -- expose OPC UA servers as i3X REST APIs",
5
+ "license": "AGPL-3.0-or-later OR LicenseRef-Sterfive-Commercial",
6
+ "author": "Sterfive SAS <contact@sterfive.com> (https://sterfive.com)",
7
+ "homepage": "https://sterfive.com",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/node-opcua/i3x2ua-node.git",
11
+ "directory": "packages/app"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "bin": {
17
+ "i3x": "./dist/cli.js"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
23
+ }
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsx src/cli.ts",
31
+ "start": "node dist/cli.js",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "dependencies": {
35
+ "@node-i3x/core": "*",
36
+ "@node-i3x/opcua-connector": "*",
37
+ "@node-i3x/rest-server": "*",
38
+ "commander": "^13.0.0",
39
+ "cosmiconfig": "^9.0.0",
40
+ "fastify": "^5.0.0",
41
+ "@fastify/cors": "^11.0.0",
42
+ "fastify-plugin": "^5.0.0",
43
+ "js-yaml": "^4.1.0",
44
+ "node-opcua": "^2.128.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/js-yaml": "^4.0.9",
48
+ "tsup": "^8.0.0",
49
+ "tsx": "^4.16.0"
50
+ }
51
+ }
package/src/banner.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { I3xConfig } from './config.js';
2
+
3
+ export function printBanner(
4
+ version: string,
5
+ config: I3xConfig,
6
+ nodeCount?: number,
7
+ ): void {
8
+ const lines = [
9
+ '',
10
+ ' i3X Server v' + version,
11
+ '',
12
+ ' OPC UA: ' + config.endpoint,
13
+ ' REST: http://' + config.host + ':' + config.port,
14
+ ];
15
+
16
+ if (nodeCount !== undefined) {
17
+ lines.push(' Model: ' + nodeCount + ' nodes');
18
+ }
19
+
20
+ lines.push('');
21
+ lines.push(' Press Ctrl+C to stop');
22
+ lines.push('');
23
+
24
+ // Calculate box width
25
+ const maxLen = Math.max(...lines.map((l) => l.length));
26
+ const width = maxLen + 2;
27
+
28
+ const hr = '-'.repeat(width);
29
+ const top = ' +' + hr + '+';
30
+ const bot = ' +' + hr + '+';
31
+
32
+ console.log(top);
33
+ for (const line of lines) {
34
+ console.log(' |' + line.padEnd(width) + '|');
35
+ }
36
+ console.log(bot);
37
+ console.log();
38
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { createRequire } from 'node:module';
2
+ import { Command } from 'commander';
3
+ import type { I3xConfig } from './config.js';
4
+ import { resolveConfig } from './config.js';
5
+ import { startServer } from './server.js';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const pkg = require('../package.json') as { version: string };
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('i3x')
14
+ .description('Expose OPC UA servers as i3X REST APIs')
15
+ .version(pkg.version)
16
+ .option('-e, --endpoint <url>', 'OPC UA endpoint URL')
17
+ .option('-p, --port <port>', 'REST API port', parseInt)
18
+ .option('-H, --host <host>', 'REST API bind address')
19
+ .option('--security-mode <mode>', 'OPC UA security mode')
20
+ .option(
21
+ '--optimized-client <mode>',
22
+ 'Optimized client: auto | disabled',
23
+ )
24
+ .option(
25
+ '--subscription-interval <seconds>',
26
+ 'Subscription interval in seconds',
27
+ parseInt,
28
+ )
29
+ .option('--log-level <level>', 'Log level: debug | info | warn | error')
30
+ .option('--no-model-preload', 'Skip model preload on startup')
31
+ .option('-c, --config <path>', 'Path to config file')
32
+ .action(async (opts) => {
33
+ // Build partial config from CLI args
34
+ const cliArgs: Partial<I3xConfig> = {};
35
+ if (opts.endpoint) cliArgs.endpoint = opts.endpoint;
36
+ if (opts.port !== undefined) cliArgs.port = opts.port;
37
+ if (opts.host) cliArgs.host = opts.host;
38
+ if (opts.securityMode) cliArgs.securityMode = opts.securityMode;
39
+ if (opts.optimizedClient)
40
+ cliArgs.optimizedClient = opts.optimizedClient;
41
+ if (opts.subscriptionInterval !== undefined)
42
+ cliArgs.subscriptionInterval = opts.subscriptionInterval;
43
+ if (opts.logLevel) cliArgs.logLevel = opts.logLevel;
44
+ if (opts.modelPreload === false) cliArgs.modelPreload = false;
45
+
46
+ const config = await resolveConfig(cliArgs, opts.config);
47
+ await startServer(config, pkg.version);
48
+ });
49
+
50
+ program.parse();
package/src/config.ts ADDED
@@ -0,0 +1,119 @@
1
+ import { cosmiconfig } from 'cosmiconfig';
2
+ import { load as loadYaml } from 'js-yaml';
3
+
4
+ export interface I3xConfig {
5
+ endpoint: string;
6
+ port: number;
7
+ host: string;
8
+ securityMode: string;
9
+ optimizedClient: 'auto' | 'disabled';
10
+ subscriptionInterval: number;
11
+ logLevel: string;
12
+ modelPreload: boolean;
13
+ failOnPreloadError: boolean;
14
+ }
15
+
16
+ const DEFAULTS: I3xConfig = {
17
+ endpoint: 'opc.tcp://localhost:4840',
18
+ port: 8080,
19
+ host: '0.0.0.0',
20
+ securityMode: 'None',
21
+ optimizedClient: 'auto',
22
+ subscriptionInterval: 5,
23
+ logLevel: 'info',
24
+ modelPreload: true,
25
+ failOnPreloadError: false,
26
+ };
27
+
28
+ // ── Environment variable helpers ───────────────────────────
29
+ function envStr(key: string): string | undefined {
30
+ return process.env[key];
31
+ }
32
+ function envInt(key: string): number | undefined {
33
+ const v = process.env[key];
34
+ return v ? parseInt(v, 10) : undefined;
35
+ }
36
+ function envBool(key: string): boolean | undefined {
37
+ const v = process.env[key];
38
+ if (!v) return undefined;
39
+ return v === '1' || v.toLowerCase() === 'true';
40
+ }
41
+
42
+ function fromEnv(): Partial<I3xConfig> {
43
+ const result: Partial<I3xConfig> = {};
44
+ const endpoint = envStr('I3X_OPCUA_ENDPOINT') ?? envStr('I3X_ENDPOINT');
45
+ if (endpoint) result.endpoint = endpoint;
46
+
47
+ const port = envInt('I3X_PORT');
48
+ if (port !== undefined) result.port = port;
49
+
50
+ const host = envStr('I3X_HOST');
51
+ if (host) result.host = host;
52
+
53
+ const securityMode = envStr('I3X_OPCUA_SECURITY_MODE');
54
+ if (securityMode) result.securityMode = securityMode;
55
+
56
+ const optimizedClient = envStr('I3X_OPCUA_OPTIMIZED_CLIENT');
57
+ if (optimizedClient === 'auto' || optimizedClient === 'disabled')
58
+ result.optimizedClient = optimizedClient;
59
+
60
+ const interval = envInt('I3X_SUBSCRIPTION_INTERVAL_SECONDS');
61
+ if (interval !== undefined) result.subscriptionInterval = interval;
62
+
63
+ const logLevel = envStr('I3X_LOG_LEVEL');
64
+ if (logLevel) result.logLevel = logLevel;
65
+
66
+ const preload = envBool('I3X_MODEL_PRELOAD_ON_STARTUP');
67
+ if (preload !== undefined) result.modelPreload = preload;
68
+
69
+ const failOnPreload = envBool('I3X_FAIL_STARTUP_ON_MODEL_PRELOAD_ERROR');
70
+ if (failOnPreload !== undefined) result.failOnPreloadError = failOnPreload;
71
+
72
+ return result;
73
+ }
74
+
75
+ // ── Config file discovery ──────────────────────────────────
76
+ export async function resolveConfig(
77
+ cliArgs: Partial<I3xConfig>,
78
+ configPath?: string,
79
+ ): Promise<I3xConfig> {
80
+ // 1. Load config file (i3x.config.yml, i3x.config.json, ...)
81
+ let fileConfig: Partial<I3xConfig> = {};
82
+ const explorer = cosmiconfig('i3x', {
83
+ searchPlaces: [
84
+ 'i3x.config.yml',
85
+ 'i3x.config.yaml',
86
+ 'i3x.config.json',
87
+ '.i3xrc',
88
+ '.i3xrc.json',
89
+ '.i3xrc.yml',
90
+ '.i3xrc.yaml',
91
+ 'package.json',
92
+ ],
93
+ loaders: {
94
+ '.yml': (_filepath: string, content: string) => loadYaml(content),
95
+ '.yaml': (_filepath: string, content: string) => loadYaml(content),
96
+ },
97
+ });
98
+
99
+ try {
100
+ const result = configPath
101
+ ? await explorer.load(configPath)
102
+ : await explorer.search();
103
+ if (result && !result.isEmpty) {
104
+ fileConfig = result.config as Partial<I3xConfig>;
105
+ }
106
+ } catch {
107
+ // No config file found -- that's fine
108
+ }
109
+
110
+ // 2. Layer: defaults < config file < env vars < CLI args
111
+ const envConfig = fromEnv();
112
+
113
+ return {
114
+ ...DEFAULTS,
115
+ ...fileConfig,
116
+ ...envConfig,
117
+ ...cliArgs,
118
+ };
119
+ }