@uns-kit/core 2.0.70 → 2.0.71
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/LICENSE +21 -21
- package/README.md +304 -302
- package/dist/base-path.js.map +1 -1
- package/dist/config/app-config.js.map +1 -1
- package/dist/config/project.config.extension.js.map +1 -1
- package/dist/config-file.js.map +1 -1
- package/dist/examples/datahub-client.js.map +1 -1
- package/dist/graphql/schema.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/tools/auth/auth-client.js.map +1 -1
- package/dist/tools/auth/index.js.map +1 -1
- package/dist/tools/auth/secure-store.js.map +1 -1
- package/dist/tools/base-path.js.map +1 -1
- package/dist/tools/datahub/datahub-client.js.map +1 -1
- package/dist/tools/file-utils.js.map +1 -1
- package/dist/tools/generate-config-schema.js.map +1 -1
- package/dist/tools/generate-uns-dictionary.js +7 -7
- package/dist/tools/generate-uns-dictionary.js.map +1 -1
- package/dist/tools/generate-uns-measurements.js +7 -7
- package/dist/tools/generate-uns-measurements.js.map +1 -1
- package/dist/tools/generate-uns-reference.js +9 -9
- package/dist/tools/generate-uns-reference.js.map +1 -1
- package/dist/tools/pull-request.js.map +1 -1
- package/dist/tools/refresh-uns.js.map +1 -1
- package/dist/tools/schema.js.map +1 -1
- package/dist/tools/sync-uns-metadata.js +22 -22
- package/dist/tools/sync-uns-metadata.js.map +1 -1
- package/dist/tools/sync-uns-schema.js +17 -17
- package/dist/tools/sync-uns-schema.js.map +1 -1
- package/dist/uns/handover-manager-event-emitter.js.map +1 -1
- package/dist/uns/handover-manager.js.map +1 -1
- package/dist/uns/process-config.js.map +1 -1
- package/dist/uns/service-metadata.js.map +1 -1
- package/dist/uns/status-monitor.js.map +1 -1
- package/dist/uns/uns-asset.js.map +1 -1
- package/dist/uns/uns-attributes.js.map +1 -1
- package/dist/uns/uns-dictionary-registry.js.map +1 -1
- package/dist/uns/uns-dictionary.generated.js.map +1 -1
- package/dist/uns/uns-event-emitter.js.map +1 -1
- package/dist/uns/uns-interfaces.js.map +1 -1
- package/dist/uns/uns-measurements.generated.js.map +1 -1
- package/dist/uns/uns-measurements.js.map +1 -1
- package/dist/uns/uns-object.js.map +1 -1
- package/dist/uns/uns-packet.js.map +1 -1
- package/dist/uns/uns-path.js.map +1 -1
- package/dist/uns/uns-proxy-process.d.ts +2 -0
- package/dist/uns/uns-proxy-process.d.ts.map +1 -1
- package/dist/uns/uns-proxy-process.js +34 -9
- package/dist/uns/uns-proxy-process.js.map +1 -1
- package/dist/uns/uns-proxy.js.map +1 -1
- package/dist/uns/uns-tags.js.map +1 -1
- package/dist/uns/uns-topic-matcher.js.map +1 -1
- package/dist/uns/uns-topics.js.map +1 -1
- package/dist/uns-config/config-schema.js.map +1 -1
- package/dist/uns-config/host-placeholders.js.map +1 -1
- package/dist/uns-config/schema-tolls.js.map +1 -1
- package/dist/uns-config/schema-tools.js.map +1 -1
- package/dist/uns-config/secret-placeholders.js.map +1 -1
- package/dist/uns-config/secret-resolver.js.map +1 -1
- package/dist/uns-config/uns-core-schema.js.map +1 -1
- package/dist/uns-grpc/uns-gateway-cli.js.map +1 -1
- package/dist/uns-grpc/uns-gateway-server.d.ts.map +1 -1
- package/dist/uns-grpc/uns-gateway-server.js +11 -2
- package/dist/uns-grpc/uns-gateway-server.js.map +1 -1
- package/dist/uns-grpc/uns-gateway.proto +107 -107
- package/dist/uns-mqtt/mqtt-interfaces.js.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.d.ts +1 -1
- package/dist/uns-mqtt/mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.js +33 -34
- package/dist/uns-mqtt/mqtt-proxy.js.map +1 -1
- package/dist/uns-mqtt/mqtt-topic-builder.js.map +1 -1
- package/dist/uns-mqtt/mqtt-worker-init.js.map +1 -1
- package/dist/uns-mqtt/mqtt-worker.js.map +1 -1
- package/dist/uns-mqtt/throttled-queue.d.ts.map +1 -1
- package/dist/uns-mqtt/throttled-queue.js +7 -2
- package/dist/uns-mqtt/throttled-queue.js.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts +5 -0
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.js +78 -34
- package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
- package/dist/uns-mqtt/ws-proxy.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,332 +1,334 @@
|
|
|
1
|
-
# @uns-kit/core
|
|
2
|
-
|
|
3
|
-
Core utilities and runtime building blocks for Unified Namespace (UNS) applications. The package bundles the process lifecycle manager, MQTT integrations, gRPC gateway helpers, configuration tooling, and shared type definitions that power the UNS ecosystem.
|
|
4
|
-
|
|
5
|
-
Note: Apps built with uns-kit are intended to be managed by the **UNS Datahub controller**.
|
|
6
|
-
|
|
7
|
-
## uns-kit in context
|
|
8
|
-
|
|
9
|
-
| Package | Description |
|
|
10
|
-
| --- | --- |
|
|
11
|
-
| [`@uns-kit/core`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-core) | Base runtime (UnsProxyProcess, MQTT helpers, config tooling, gRPC gateway). |
|
|
12
|
-
| [`@uns-kit/api`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-api) | Express plugin — HTTP endpoints, JWT/JWKS auth, Swagger, UNS metadata. |
|
|
13
|
-
| [`@uns-kit/cron`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-cron) | Cron-driven scheduler that emits UNS events on a fixed cadence. |
|
|
14
|
-
| [`@uns-kit/cli`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-cli) | CLI for scaffolding new UNS applications. |
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
pnpm add @uns-kit/core
|
|
20
|
-
# or
|
|
21
|
-
npm install @uns-kit/core
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Key concepts
|
|
25
|
-
|
|
26
|
-
- **UnsProxyProcess** — the central runtime class. It manages the MQTT connection, plugin lifecycle, and the instance status topic. Plugins (`@uns-kit/api`, `@uns-kit/cron`) augment it with domain-specific proxy factories.
|
|
27
|
-
- **UnsProxy** — base class extended by all plugin proxies. Tracks produced topics, API endpoints, and catch-all mappings; re-publishes them to the controller on a 60-second cadence.
|
|
28
|
-
- **ConfigFile** — loads and validates `config.json` at startup. On a real server this file is provided by the UNS Datahub controller; in development you maintain it yourself.
|
|
29
|
-
- **MQTT helpers** — resilient publishers, topic builders, throttled queues, and handover support.
|
|
30
|
-
- **gRPC gateway** — infrastructure to bridge Python workers into the UNS message fabric.
|
|
31
|
-
|
|
32
|
-
## Basic usage
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import UnsProxyProcess from "@uns-kit/core/uns/uns-proxy-process";
|
|
36
|
-
import { ConfigFile } from "@uns-kit/core";
|
|
37
|
-
|
|
38
|
-
const config = await ConfigFile.loadConfig();
|
|
39
|
-
|
|
40
|
-
// Connect to MQTT broker; processName identifies this service in the controller.
|
|
41
|
-
const proc = new UnsProxyProcess(config.infra.host!, { processName: config.uns.processName });
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Publish retained service metadata when a runtime should be discoverable by the
|
|
45
|
-
controller as a core service, addon, or built-in capability:
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
await proc.publishServiceMetadata({
|
|
49
|
-
serviceId: "uns-bridge-mqtt",
|
|
50
|
-
kind: "addon",
|
|
51
|
-
addonId: "uns-bridge-mqtt",
|
|
52
|
-
capabilities: ["mqtt-source-browser", "runtime-mappings"],
|
|
53
|
-
apiRoutes: [
|
|
54
|
-
{
|
|
55
|
-
path: "/api/system/bridge/mqtt/runtime/service/bridge/health",
|
|
56
|
-
kind: "health",
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
The metadata is retained on
|
|
63
|
-
`uns-infra/<package>/<version>/<processName>/service-metadata`. Controllers
|
|
64
|
-
combine this identity/capability payload with live process telemetry and route
|
|
65
|
-
health before marking a service healthy.
|
|
66
|
-
|
|
67
|
-
Extend it with plugins:
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import "@uns-kit/api";
|
|
71
|
-
import "@uns-kit/cron";
|
|
72
|
-
import { type UnsProxyProcessWithApi } from "@uns-kit/api";
|
|
73
|
-
import { type UnsProxyProcessWithCron } from "@uns-kit/cron";
|
|
74
|
-
|
|
75
|
-
const proc = new UnsProxyProcess(config.infra.host!, { processName: config.uns.processName })
|
|
76
|
-
as UnsProxyProcessWithApi & UnsProxyProcessWithCron;
|
|
77
|
-
|
|
78
|
-
const api = await proc.createApiProxy("my-service", { jwtSecret: "CHANGEME" });
|
|
79
|
-
const cron = await proc.createCrontabProxy("*/5 * * * *", { event: "tick" });
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Datahub client (last value + history)
|
|
83
|
-
|
|
84
|
-
`UnsClient` provides a minimal REST client for the UNS Datahub API, including batch last-value, single-topic catch-all history, and batch range endpoints. Prefer a long-lived service token if available; you can pass it directly and skip username/password auth.
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
import { UnsClient } from "@uns-kit/core";
|
|
88
|
-
import { ConfigFile } from "@uns-kit/core";
|
|
89
|
-
|
|
90
|
-
const config = await ConfigFile.loadConfig();
|
|
91
|
-
|
|
92
|
-
const client = new UnsClient("https://datahub.example.com", {
|
|
93
|
-
token: process.env.UNS_SERVICE_TOKEN ?? config.uns.token,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const values = await client.lastValue([
|
|
97
|
-
"raw/data/line-1/motor/main/temperature",
|
|
98
|
-
"raw/data/line-1/motor/main/status",
|
|
99
|
-
]);
|
|
100
|
-
console.log(values);
|
|
101
|
-
|
|
102
|
-
const data = await client.getAttributeData("sij/acroni/vv/hrm-furnace/equipment/pusher/output-quantity", {
|
|
103
|
-
from: "2026-05-07T11:17:01.157Z",
|
|
104
|
-
to: "2026-05-07T11:22:01.157Z",
|
|
105
|
-
table: "uns_sij_hrm_furnace_data",
|
|
106
|
-
aggregate: "last",
|
|
107
|
-
dedupe: false,
|
|
108
|
-
});
|
|
109
|
-
console.log(data?.toRecords());
|
|
110
|
-
|
|
111
|
-
const customData = await client.getData("/projects/project-name/path-to-data/data", {
|
|
112
|
-
fromDate: "20260325",
|
|
113
|
-
});
|
|
114
|
-
console.log(await customData.json());
|
|
115
|
-
|
|
116
|
-
const batchHistory = await client.history([
|
|
117
|
-
"sij/acroni/vv/hrm-furnace/equipment/zone-1/temperature",
|
|
118
|
-
"sij/acroni/vv/hrm-furnace/equipment/zone-2/temperature",
|
|
119
|
-
], {
|
|
120
|
-
from: "2026-04-09T06:00:00Z",
|
|
121
|
-
to: "2026-04-09T07:00:00Z",
|
|
122
|
-
limit: 500,
|
|
123
|
-
});
|
|
124
|
-
console.log(batchHistory?.byTopic);
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Sub-Asset Publishing
|
|
128
|
-
|
|
129
|
-
Sub-assets use the same publish fields as normal assets. Set `topic` to the full
|
|
130
|
-
parent asset path and set `asset` to the leaf sub-asset:
|
|
131
|
-
|
|
132
|
-
```ts
|
|
1
|
+
# @uns-kit/core
|
|
2
|
+
|
|
3
|
+
Core utilities and runtime building blocks for Unified Namespace (UNS) applications. The package bundles the process lifecycle manager, MQTT integrations, gRPC gateway helpers, configuration tooling, and shared type definitions that power the UNS ecosystem.
|
|
4
|
+
|
|
5
|
+
Note: Apps built with uns-kit are intended to be managed by the **UNS Datahub controller**.
|
|
6
|
+
|
|
7
|
+
## uns-kit in context
|
|
8
|
+
|
|
9
|
+
| Package | Description |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| [`@uns-kit/core`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-core) | Base runtime (UnsProxyProcess, MQTT helpers, config tooling, gRPC gateway). |
|
|
12
|
+
| [`@uns-kit/api`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-api) | Express plugin — HTTP endpoints, JWT/JWKS auth, Swagger, UNS metadata. |
|
|
13
|
+
| [`@uns-kit/cron`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-cron) | Cron-driven scheduler that emits UNS events on a fixed cadence. |
|
|
14
|
+
| [`@uns-kit/cli`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-cli) | CLI for scaffolding new UNS applications. |
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @uns-kit/core
|
|
20
|
+
# or
|
|
21
|
+
npm install @uns-kit/core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Key concepts
|
|
25
|
+
|
|
26
|
+
- **UnsProxyProcess** — the central runtime class. It manages the MQTT connection, plugin lifecycle, and the instance status topic. Plugins (`@uns-kit/api`, `@uns-kit/cron`) augment it with domain-specific proxy factories.
|
|
27
|
+
- **UnsProxy** — base class extended by all plugin proxies. Tracks produced topics, API endpoints, and catch-all mappings; re-publishes them to the controller on a 60-second cadence.
|
|
28
|
+
- **ConfigFile** — loads and validates `config.json` at startup. On a real server this file is provided by the UNS Datahub controller; in development you maintain it yourself.
|
|
29
|
+
- **MQTT helpers** — resilient publishers, topic builders, throttled queues, and handover support.
|
|
30
|
+
- **gRPC gateway** — infrastructure to bridge Python workers into the UNS message fabric.
|
|
31
|
+
|
|
32
|
+
## Basic usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import UnsProxyProcess from "@uns-kit/core/uns/uns-proxy-process";
|
|
36
|
+
import { ConfigFile } from "@uns-kit/core";
|
|
37
|
+
|
|
38
|
+
const config = await ConfigFile.loadConfig();
|
|
39
|
+
|
|
40
|
+
// Connect to MQTT broker; processName identifies this service in the controller.
|
|
41
|
+
const proc = new UnsProxyProcess(config.infra.host!, { processName: config.uns.processName });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Publish retained service metadata when a runtime should be discoverable by the
|
|
45
|
+
controller as a core service, addon, or built-in capability:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
await proc.publishServiceMetadata({
|
|
49
|
+
serviceId: "uns-bridge-mqtt",
|
|
50
|
+
kind: "addon",
|
|
51
|
+
addonId: "uns-bridge-mqtt",
|
|
52
|
+
capabilities: ["mqtt-source-browser", "runtime-mappings"],
|
|
53
|
+
apiRoutes: [
|
|
54
|
+
{
|
|
55
|
+
path: "/api/system/bridge/mqtt/runtime/service/bridge/health",
|
|
56
|
+
kind: "health",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The metadata is retained on
|
|
63
|
+
`uns-infra/<package>/<version>/<processName>/service-metadata`. Controllers
|
|
64
|
+
combine this identity/capability payload with live process telemetry and route
|
|
65
|
+
health before marking a service healthy.
|
|
66
|
+
|
|
67
|
+
Extend it with plugins:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import "@uns-kit/api";
|
|
71
|
+
import "@uns-kit/cron";
|
|
72
|
+
import { type UnsProxyProcessWithApi } from "@uns-kit/api";
|
|
73
|
+
import { type UnsProxyProcessWithCron } from "@uns-kit/cron";
|
|
74
|
+
|
|
75
|
+
const proc = new UnsProxyProcess(config.infra.host!, { processName: config.uns.processName })
|
|
76
|
+
as UnsProxyProcessWithApi & UnsProxyProcessWithCron;
|
|
77
|
+
|
|
78
|
+
const api = await proc.createApiProxy("my-service", { jwtSecret: "CHANGEME" });
|
|
79
|
+
const cron = await proc.createCrontabProxy("*/5 * * * *", { event: "tick" });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Datahub client (last value + history)
|
|
83
|
+
|
|
84
|
+
`UnsClient` provides a minimal REST client for the UNS Datahub API, including batch last-value, single-topic catch-all history, and batch range endpoints. Prefer a long-lived service token if available; you can pass it directly and skip username/password auth.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { UnsClient } from "@uns-kit/core";
|
|
88
|
+
import { ConfigFile } from "@uns-kit/core";
|
|
89
|
+
|
|
90
|
+
const config = await ConfigFile.loadConfig();
|
|
91
|
+
|
|
92
|
+
const client = new UnsClient("https://datahub.example.com", {
|
|
93
|
+
token: process.env.UNS_SERVICE_TOKEN ?? config.uns.token,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const values = await client.lastValue([
|
|
97
|
+
"raw/data/line-1/motor/main/temperature",
|
|
98
|
+
"raw/data/line-1/motor/main/status",
|
|
99
|
+
]);
|
|
100
|
+
console.log(values);
|
|
101
|
+
|
|
102
|
+
const data = await client.getAttributeData("sij/acroni/vv/hrm-furnace/equipment/pusher/output-quantity", {
|
|
103
|
+
from: "2026-05-07T11:17:01.157Z",
|
|
104
|
+
to: "2026-05-07T11:22:01.157Z",
|
|
105
|
+
table: "uns_sij_hrm_furnace_data",
|
|
106
|
+
aggregate: "last",
|
|
107
|
+
dedupe: false,
|
|
108
|
+
});
|
|
109
|
+
console.log(data?.toRecords());
|
|
110
|
+
|
|
111
|
+
const customData = await client.getData("/projects/project-name/path-to-data/data", {
|
|
112
|
+
fromDate: "20260325",
|
|
113
|
+
});
|
|
114
|
+
console.log(await customData.json());
|
|
115
|
+
|
|
116
|
+
const batchHistory = await client.history([
|
|
117
|
+
"sij/acroni/vv/hrm-furnace/equipment/zone-1/temperature",
|
|
118
|
+
"sij/acroni/vv/hrm-furnace/equipment/zone-2/temperature",
|
|
119
|
+
], {
|
|
120
|
+
from: "2026-04-09T06:00:00Z",
|
|
121
|
+
to: "2026-04-09T07:00:00Z",
|
|
122
|
+
limit: 500,
|
|
123
|
+
});
|
|
124
|
+
console.log(batchHistory?.byTopic);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Sub-Asset Publishing
|
|
128
|
+
|
|
129
|
+
Sub-assets use the same publish fields as normal assets. Set `topic` to the full
|
|
130
|
+
parent asset path and set `asset` to the leaf sub-asset:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
133
|
await proxy.publishMqttMessage({
|
|
134
134
|
topic: "sij/acroni/jek/pp/",
|
|
135
135
|
asset: "furnace-1",
|
|
136
|
-
objectType: "material",
|
|
137
|
-
objectId: "main",
|
|
138
|
-
attributes: {
|
|
139
|
-
attribute: "daily-production",
|
|
140
|
-
data: {
|
|
141
|
-
time: new Date().toISOString(),
|
|
142
|
-
value: 42,
|
|
143
|
-
uom: "t",
|
|
136
|
+
objectType: "material",
|
|
137
|
+
objectId: "main",
|
|
138
|
+
attributes: {
|
|
139
|
+
attribute: "daily-production",
|
|
140
|
+
data: {
|
|
141
|
+
time: new Date().toISOString(),
|
|
142
|
+
value: 42,
|
|
143
|
+
uom: "t",
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
146
|
});
|
|
147
147
|
await proxy.flush();
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
This publishes
|
|
151
|
-
`sij/acroni/jek/pp/furnace-1/material/main/daily-production`. Consumers that
|
|
152
|
-
persist the existing QuestDB identity columns should store
|
|
153
|
-
`topic = "sij/acroni/jek/pp"` and `asset = "furnace-1"`. Do not use `dataGroup`
|
|
154
|
-
for sub-asset hierarchy; it is storage/routing metadata.
|
|
155
|
-
|
|
156
|
-
## Validity / Liveliness
|
|
157
|
-
|
|
158
|
-
UNS attributes can declare how the controller decides whether they are live or stale; in most apps this is primarily used to drive UI liveliness/activity indicators. In app-level modeling we use two modes only:
|
|
159
|
-
|
|
160
|
-
- `interval`: continuously refreshed values (stale after ~2× `expectedIntervalMs`)
|
|
161
|
-
- `lifecycle`: event-driven activity that stays active until a defined end value (`lifecycleEndValue`)
|
|
162
|
-
|
|
163
|
-
```ts
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
This publishes
|
|
151
|
+
`sij/acroni/jek/pp/furnace-1/material/main/daily-production`. Consumers that
|
|
152
|
+
persist the existing QuestDB identity columns should store
|
|
153
|
+
`topic = "sij/acroni/jek/pp"` and `asset = "furnace-1"`. Do not use `dataGroup`
|
|
154
|
+
for sub-asset hierarchy; it is storage/routing metadata.
|
|
155
|
+
|
|
156
|
+
## Validity / Liveliness
|
|
157
|
+
|
|
158
|
+
UNS attributes can declare how the controller decides whether they are live or stale; in most apps this is primarily used to drive UI liveliness/activity indicators. In app-level modeling we use two modes only:
|
|
159
|
+
|
|
160
|
+
- `interval`: continuously refreshed values (stale after ~2× `expectedIntervalMs`)
|
|
161
|
+
- `lifecycle`: event-driven activity that stays active until a defined end value (`lifecycleEndValue`)
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
164
|
await proxy.publishMqttMessage({
|
|
165
165
|
topic: "raw/data/",
|
|
166
166
|
asset: "line-1",
|
|
167
|
-
objectType: "motor",
|
|
168
|
-
objectId: "main",
|
|
169
|
-
attributes: {
|
|
170
|
-
attribute: "status",
|
|
171
|
-
data: { time: new Date().toISOString(), value: "RUNNING" },
|
|
172
|
-
validityMode: "lifecycle",
|
|
167
|
+
objectType: "motor",
|
|
168
|
+
objectId: "main",
|
|
169
|
+
attributes: {
|
|
170
|
+
attribute: "status",
|
|
171
|
+
data: { time: new Date().toISOString(), value: "RUNNING" },
|
|
172
|
+
validityMode: "lifecycle",
|
|
173
173
|
lifecycleEndValue: "STOPPED",
|
|
174
174
|
},
|
|
175
175
|
});
|
|
176
176
|
await proxy.flush();
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Counter Attributes
|
|
180
|
-
|
|
181
|
-
Publish cumulative counters as raw counter state. Do not use producer-side
|
|
182
|
-
delta modes for new code; `MessageMode.Delta`, `MessageMode.Both`, and gRPC
|
|
183
|
-
`value_is_cumulative` are deprecated because producer memory is lost across
|
|
184
|
-
service restarts. Datahub history APIs should calculate delta/rate from
|
|
185
|
-
persisted rows.
|
|
186
|
-
|
|
187
|
-
For a `Data` attribute, mark the series directly:
|
|
188
|
-
|
|
189
|
-
```ts
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Counter Attributes
|
|
180
|
+
|
|
181
|
+
Publish cumulative counters as raw counter state. Do not use producer-side
|
|
182
|
+
delta modes for new code; `MessageMode.Delta`, `MessageMode.Both`, and gRPC
|
|
183
|
+
`value_is_cumulative` are deprecated because producer memory is lost across
|
|
184
|
+
service restarts. Datahub history APIs should calculate delta/rate from
|
|
185
|
+
persisted rows.
|
|
186
|
+
|
|
187
|
+
For a `Data` attribute, mark the series directly:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
190
|
await proxy.publishMqttMessage({
|
|
191
191
|
topic: "raw/data/",
|
|
192
192
|
asset: "line-1",
|
|
193
|
-
objectType: "energy-resource",
|
|
194
|
-
objectId: "main",
|
|
195
|
-
attributes: {
|
|
196
|
-
attribute: "active-energy-total",
|
|
197
|
-
description: "Cumulative active energy counter",
|
|
198
|
-
valueType: "number",
|
|
199
|
-
presentationKind: "counter",
|
|
200
|
-
defaultAggregation: "last",
|
|
201
|
-
counterResetPolicy: "new-value",
|
|
202
|
-
data: {
|
|
203
|
-
time: new Date().toISOString(),
|
|
204
|
-
value: 12345.6,
|
|
205
|
-
uom: "kWh",
|
|
206
|
-
dataGroup: "metering",
|
|
193
|
+
objectType: "energy-resource",
|
|
194
|
+
objectId: "main",
|
|
195
|
+
attributes: {
|
|
196
|
+
attribute: "active-energy-total",
|
|
197
|
+
description: "Cumulative active energy counter",
|
|
198
|
+
valueType: "number",
|
|
199
|
+
presentationKind: "counter",
|
|
200
|
+
defaultAggregation: "last",
|
|
201
|
+
counterResetPolicy: "new-value",
|
|
202
|
+
data: {
|
|
203
|
+
time: new Date().toISOString(),
|
|
204
|
+
value: 12345.6,
|
|
205
|
+
uom: "kWh",
|
|
206
|
+
dataGroup: "metering",
|
|
207
207
|
},
|
|
208
208
|
},
|
|
209
209
|
});
|
|
210
210
|
await proxy.flush();
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
For a `Table` attribute, keep the table as the source row and mark chartable
|
|
214
|
-
counter columns with `tableColumns`:
|
|
215
|
-
|
|
216
|
-
```ts
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
For a `Table` attribute, keep the table as the source row and mark chartable
|
|
214
|
+
counter columns with `tableColumns`:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
217
|
await proxy.publishMqttMessage({
|
|
218
218
|
topic: "raw/data/",
|
|
219
219
|
asset: "line-1",
|
|
220
|
-
objectType: "energy-resource",
|
|
221
|
-
objectId: "main",
|
|
222
|
-
attributes: {
|
|
223
|
-
attribute: "measurements",
|
|
224
|
-
description: "Metering table",
|
|
225
|
-
tableColumns: [
|
|
226
|
-
{
|
|
227
|
-
name: "active_energy_total",
|
|
228
|
-
valueType: "number",
|
|
229
|
-
presentationKind: "counter",
|
|
230
|
-
defaultAggregation: "last",
|
|
231
|
-
counterResetPolicy: "new-value",
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
table: {
|
|
235
|
-
time: new Date().toISOString(),
|
|
236
|
-
dataGroup: "metering",
|
|
237
|
-
columns: [
|
|
238
|
-
{ name: "active_energy_total", type: "double", value: 12345.6, uom: "kWh" },
|
|
239
|
-
{ name: "power", type: "double", value: 42.1, uom: "kW" },
|
|
240
|
-
],
|
|
220
|
+
objectType: "energy-resource",
|
|
221
|
+
objectId: "main",
|
|
222
|
+
attributes: {
|
|
223
|
+
attribute: "measurements",
|
|
224
|
+
description: "Metering table",
|
|
225
|
+
tableColumns: [
|
|
226
|
+
{
|
|
227
|
+
name: "active_energy_total",
|
|
228
|
+
valueType: "number",
|
|
229
|
+
presentationKind: "counter",
|
|
230
|
+
defaultAggregation: "last",
|
|
231
|
+
counterResetPolicy: "new-value",
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
table: {
|
|
235
|
+
time: new Date().toISOString(),
|
|
236
|
+
dataGroup: "metering",
|
|
237
|
+
columns: [
|
|
238
|
+
{ name: "active_energy_total", type: "double", value: 12345.6, uom: "kWh" },
|
|
239
|
+
{ name: "power", type: "double", value: 42.1, uom: "kW" },
|
|
240
|
+
],
|
|
241
241
|
},
|
|
242
242
|
},
|
|
243
243
|
});
|
|
244
244
|
await proxy.flush();
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
`dataGroup` is a storage/routing hint for consumers such as archivers. It is not
|
|
248
|
-
part of the UNS identity path and is not the same as `objectType`. For example,
|
|
249
|
-
an archiver may persist a `table` packet with `dataGroup: "metering"` into a
|
|
250
|
-
separate physical table family while the UNS path still comes from
|
|
251
|
-
`topic/asset/objectType/objectId/attribute`.
|
|
252
|
-
|
|
253
|
-
## High-throughput publishing
|
|
254
|
-
|
|
255
|
-
For higher publish rates, configure bounded parallel publishing instead of relying on a single in-flight broker callback:
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
const proxy = await proc.createUnsMqttProxy(config.infra.host!, "output", "force", true, {
|
|
259
|
-
publishThrottlingDelay: 0,
|
|
260
|
-
publishConcurrency: 16,
|
|
261
|
-
maxPendingPublishes: 5000,
|
|
262
|
-
});
|
|
263
|
-
```
|
|
264
|
-
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`dataGroup` is a storage/routing hint for consumers such as archivers. It is not
|
|
248
|
+
part of the UNS identity path and is not the same as `objectType`. For example,
|
|
249
|
+
an archiver may persist a `table` packet with `dataGroup: "metering"` into a
|
|
250
|
+
separate physical table family while the UNS path still comes from
|
|
251
|
+
`topic/asset/objectType/objectId/attribute`.
|
|
252
|
+
|
|
253
|
+
## High-throughput publishing
|
|
254
|
+
|
|
255
|
+
For higher publish rates, configure bounded parallel publishing instead of relying on a single in-flight broker callback:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const proxy = await proc.createUnsMqttProxy(config.infra.host!, "output", "force", true, {
|
|
259
|
+
publishThrottlingDelay: 0,
|
|
260
|
+
publishConcurrency: 16,
|
|
261
|
+
maxPendingPublishes: 5000,
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
265
|
`publishMqttMessage()` resolves when a message is accepted into the local bounded publisher queue. Asynchronous broker publish failures are logged and emitted on the proxy `error` event.
|
|
266
266
|
|
|
267
|
-
If the bounded queue is full, `publishMessage()` / `publishMqttMessage()` reject
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
- `
|
|
291
|
-
- `
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
|
298
|
-
|
|
299
|
-
| `--
|
|
300
|
-
| `--
|
|
301
|
-
| `--
|
|
302
|
-
| `--
|
|
303
|
-
| `--
|
|
304
|
-
| `--
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
-
|
|
321
|
-
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
267
|
+
If the bounded queue is full, `publishMessage()` / `publishMqttMessage()` reject before another message enters the worker channel. The limit covers requests awaiting worker acceptance plus accepted queued and in-flight publishes, so callers cannot create an unbounded main-thread backlog.
|
|
268
|
+
|
|
269
|
+
Use `await proxy.flush()` or `await proxy.drainPublishes()` before assuming all accepted messages have reached the broker. `await proxy.stop()` closes publish admission immediately and drains by default with a timeout; repeated `stop()` calls share the same result. Use `await proxy.stop({ drain: false })` only when dropping queued messages is acceptable. When a proxy belongs to `UnsProxyProcess`, call `await process.shutdown()` instead of stopping the proxy separately. Process shutdown attempts every cleanup and rejects with an `AggregateError` if any drain or stop fails.
|
|
270
|
+
|
|
271
|
+
## Sync UNS schema from the controller
|
|
272
|
+
|
|
273
|
+
`sync-uns-schema` fetches the canonical UNS dictionary and measurements from the controller REST API and refreshes local JSON files and generated TypeScript artifacts.
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Run inside a generated microservice project:
|
|
277
|
+
pnpm run sync-uns-schema
|
|
278
|
+
|
|
279
|
+
# The controller URL is read from config.json (uns.rest) automatically.
|
|
280
|
+
# You will be prompted for the bearer token if not set via env var.
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Or with explicit options:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pnpm run sync-uns-schema --controller-url http://localhost:3200 --token <bearer-token>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**What it updates (inside a generated project):**
|
|
290
|
+
- `uns-dictionary.json`
|
|
291
|
+
- `uns-measurements.json`
|
|
292
|
+
- `src/uns/uns-dictionary.generated.ts`
|
|
293
|
+
- `src/uns/uns-measurements.generated.ts`
|
|
294
|
+
|
|
295
|
+
**Options:**
|
|
296
|
+
|
|
297
|
+
| Flag | Default | Description |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| `--controller-url` | from `config.json` `uns.rest` or `UNS_CONTROLLER_URL` | Controller base URL |
|
|
300
|
+
| `--token` | interactive prompt or `UNS_CONTROLLER_TOKEN` | Admin bearer token |
|
|
301
|
+
| `--status` | `active` | Dictionary filter: `active`, `draft`, `deprecated`, `all` |
|
|
302
|
+
| `--dry-run` | — | Report changes without writing |
|
|
303
|
+
| `--dictionary-only` | — | Skip measurements sync |
|
|
304
|
+
| `--measurements-only` | — | Skip dictionary sync |
|
|
305
|
+
| `--skip-generate` | — | Skip TS regeneration |
|
|
306
|
+
| `--project-root <dir>` | auto-detect | Target project root |
|
|
307
|
+
|
|
308
|
+
## Config schema generation
|
|
309
|
+
|
|
310
|
+
Edit `src/config/project.config.extension.ts` inside your project and run:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
pnpm run generate-config-schema
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This regenerates `config.schema.json` and `src/config/app-config.ts`, keeping editor completions and runtime types in sync with your config extensions.
|
|
317
|
+
|
|
318
|
+
## Infisical secret resolution
|
|
319
|
+
|
|
320
|
+
- Looks for `INFISICAL_TOKEN` / `INFISICAL_PERSONAL_TOKEN`, then `/run/secrets/infisical_token`.
|
|
321
|
+
- If unavailable, logs a warning and returns `default` (or `undefined` for `optional: true` secrets).
|
|
322
|
+
- Required secrets throw with the original error message when Infisical is unreachable.
|
|
323
|
+
- Call `resolveInfisicalConfig()` to inspect the resolved token/projectId/siteUrl.
|
|
324
|
+
|
|
325
|
+
## Development
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
pnpm run typecheck # type-check sources
|
|
329
|
+
pnpm run build # emit JS + declarations to dist/
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
MIT © Aljoša Vister
|