@uns-kit/core 2.0.68 → 2.0.70

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.
Files changed (95) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +308 -288
  3. package/dist/base-path.js.map +1 -1
  4. package/dist/config/app-config.js.map +1 -1
  5. package/dist/config/project.config.extension.js.map +1 -1
  6. package/dist/config-file.js.map +1 -1
  7. package/dist/examples/datahub-client.js.map +1 -1
  8. package/dist/graphql/schema.js.map +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/logger.js.map +1 -1
  11. package/dist/tools/auth/auth-client.d.ts.map +1 -1
  12. package/dist/tools/auth/auth-client.js +19 -15
  13. package/dist/tools/auth/auth-client.js.map +1 -1
  14. package/dist/tools/auth/index.js.map +1 -1
  15. package/dist/tools/auth/secure-store.js.map +1 -1
  16. package/dist/tools/base-path.js.map +1 -1
  17. package/dist/tools/datahub/datahub-client.d.ts +1 -1
  18. package/dist/tools/datahub/datahub-client.d.ts.map +1 -1
  19. package/dist/tools/datahub/datahub-client.js +6 -8
  20. package/dist/tools/datahub/datahub-client.js.map +1 -1
  21. package/dist/tools/file-utils.js.map +1 -1
  22. package/dist/tools/generate-config-schema.js.map +1 -1
  23. package/dist/tools/generate-uns-dictionary.js +7 -7
  24. package/dist/tools/generate-uns-dictionary.js.map +1 -1
  25. package/dist/tools/generate-uns-measurements.js +7 -7
  26. package/dist/tools/generate-uns-measurements.js.map +1 -1
  27. package/dist/tools/generate-uns-reference.js +9 -9
  28. package/dist/tools/generate-uns-reference.js.map +1 -1
  29. package/dist/tools/pull-request.js.map +1 -1
  30. package/dist/tools/refresh-uns.js.map +1 -1
  31. package/dist/tools/schema.js.map +1 -1
  32. package/dist/tools/sync-uns-metadata.js +22 -22
  33. package/dist/tools/sync-uns-metadata.js.map +1 -1
  34. package/dist/tools/sync-uns-schema.js +17 -17
  35. package/dist/tools/sync-uns-schema.js.map +1 -1
  36. package/dist/uns/handover-manager-event-emitter.js.map +1 -1
  37. package/dist/uns/handover-manager.d.ts.map +1 -1
  38. package/dist/uns/handover-manager.js +1 -3
  39. package/dist/uns/handover-manager.js.map +1 -1
  40. package/dist/uns/process-config.js.map +1 -1
  41. package/dist/uns/service-metadata.js.map +1 -1
  42. package/dist/uns/status-monitor.js.map +1 -1
  43. package/dist/uns/uns-asset.js.map +1 -1
  44. package/dist/uns/uns-attributes.js.map +1 -1
  45. package/dist/uns/uns-dictionary-registry.js.map +1 -1
  46. package/dist/uns/uns-dictionary.generated.js.map +1 -1
  47. package/dist/uns/uns-event-emitter.js.map +1 -1
  48. package/dist/uns/uns-interfaces.d.ts +2 -0
  49. package/dist/uns/uns-interfaces.d.ts.map +1 -1
  50. package/dist/uns/uns-interfaces.js.map +1 -1
  51. package/dist/uns/uns-measurements.generated.js.map +1 -1
  52. package/dist/uns/uns-measurements.js.map +1 -1
  53. package/dist/uns/uns-object.js.map +1 -1
  54. package/dist/uns/uns-packet.js.map +1 -1
  55. package/dist/uns/uns-path.js.map +1 -1
  56. package/dist/uns/uns-proxy-process.d.ts +2 -1
  57. package/dist/uns/uns-proxy-process.d.ts.map +1 -1
  58. package/dist/uns/uns-proxy-process.js +8 -2
  59. package/dist/uns/uns-proxy-process.js.map +1 -1
  60. package/dist/uns/uns-proxy.js.map +1 -1
  61. package/dist/uns/uns-tags.js.map +1 -1
  62. package/dist/uns/uns-topic-matcher.js.map +1 -1
  63. package/dist/uns/uns-topics.js.map +1 -1
  64. package/dist/uns-config/config-schema.js.map +1 -1
  65. package/dist/uns-config/host-placeholders.js.map +1 -1
  66. package/dist/uns-config/schema-tolls.js.map +1 -1
  67. package/dist/uns-config/schema-tools.js.map +1 -1
  68. package/dist/uns-config/secret-placeholders.js.map +1 -1
  69. package/dist/uns-config/secret-resolver.js.map +1 -1
  70. package/dist/uns-config/uns-core-schema.js.map +1 -1
  71. package/dist/uns-grpc/uns-gateway-cli.js.map +1 -1
  72. package/dist/uns-grpc/uns-gateway-server.js +2 -2
  73. package/dist/uns-grpc/uns-gateway-server.js.map +1 -1
  74. package/dist/uns-grpc/uns-gateway.proto +107 -107
  75. package/dist/uns-mqtt/mqtt-interfaces.d.ts +2 -0
  76. package/dist/uns-mqtt/mqtt-interfaces.d.ts.map +1 -1
  77. package/dist/uns-mqtt/mqtt-interfaces.js.map +1 -1
  78. package/dist/uns-mqtt/mqtt-proxy.d.ts.map +1 -1
  79. package/dist/uns-mqtt/mqtt-proxy.js +2 -4
  80. package/dist/uns-mqtt/mqtt-proxy.js.map +1 -1
  81. package/dist/uns-mqtt/mqtt-topic-builder.js.map +1 -1
  82. package/dist/uns-mqtt/mqtt-worker-init.js.map +1 -1
  83. package/dist/uns-mqtt/mqtt-worker.d.ts.map +1 -1
  84. package/dist/uns-mqtt/mqtt-worker.js +33 -17
  85. package/dist/uns-mqtt/mqtt-worker.js.map +1 -1
  86. package/dist/uns-mqtt/throttled-queue.d.ts +13 -2
  87. package/dist/uns-mqtt/throttled-queue.d.ts.map +1 -1
  88. package/dist/uns-mqtt/throttled-queue.js +80 -33
  89. package/dist/uns-mqtt/throttled-queue.js.map +1 -1
  90. package/dist/uns-mqtt/uns-mqtt-proxy.d.ts +16 -3
  91. package/dist/uns-mqtt/uns-mqtt-proxy.d.ts.map +1 -1
  92. package/dist/uns-mqtt/uns-mqtt-proxy.js +104 -23
  93. package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
  94. package/dist/uns-mqtt/ws-proxy.js.map +1 -1
  95. package/package.json +1 -1
package/README.md CHANGED
@@ -1,312 +1,332 @@
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
- ```
148
-
149
- This publishes
150
- `sij/acroni/jek/pp/furnace-1/material/main/daily-production`. Consumers that
151
- persist the existing QuestDB identity columns should store
152
- `topic = "sij/acroni/jek/pp"` and `asset = "furnace-1"`. Do not use `dataGroup`
153
- for sub-asset hierarchy; it is storage/routing metadata.
154
-
155
- ## Validity / Liveliness
156
-
157
- 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:
158
-
159
- - `interval`: continuously refreshed values (stale after ~2× `expectedIntervalMs`)
160
- - `lifecycle`: event-driven activity that stays active until a defined end value (`lifecycleEndValue`)
161
-
162
- ```ts
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
163
164
  await proxy.publishMqttMessage({
164
165
  topic: "raw/data/",
165
166
  asset: "line-1",
166
- objectType: "motor",
167
- objectId: "main",
168
- attributes: {
169
- attribute: "status",
170
- data: { time: new Date().toISOString(), value: "RUNNING" },
171
- validityMode: "lifecycle",
167
+ objectType: "motor",
168
+ objectId: "main",
169
+ attributes: {
170
+ attribute: "status",
171
+ data: { time: new Date().toISOString(), value: "RUNNING" },
172
+ validityMode: "lifecycle",
172
173
  lifecycleEndValue: "STOPPED",
173
174
  },
174
175
  });
175
- ```
176
-
177
- ## Counter Attributes
178
-
179
- Publish cumulative counters as raw counter state. Do not use producer-side
180
- delta modes for new code; `MessageMode.Delta`, `MessageMode.Both`, and gRPC
181
- `value_is_cumulative` are deprecated because producer memory is lost across
182
- service restarts. Datahub history APIs should calculate delta/rate from
183
- persisted rows.
184
-
185
- For a `Data` attribute, mark the series directly:
186
-
187
- ```ts
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
188
190
  await proxy.publishMqttMessage({
189
191
  topic: "raw/data/",
190
192
  asset: "line-1",
191
- objectType: "energy-resource",
192
- objectId: "main",
193
- attributes: {
194
- attribute: "active-energy-total",
195
- description: "Cumulative active energy counter",
196
- valueType: "number",
197
- presentationKind: "counter",
198
- defaultAggregation: "last",
199
- counterResetPolicy: "new-value",
200
- data: {
201
- time: new Date().toISOString(),
202
- value: 12345.6,
203
- uom: "kWh",
204
- 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",
205
207
  },
206
208
  },
207
209
  });
208
- ```
209
-
210
- For a `Table` attribute, keep the table as the source row and mark chartable
211
- counter columns with `tableColumns`:
212
-
213
- ```ts
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
214
217
  await proxy.publishMqttMessage({
215
218
  topic: "raw/data/",
216
219
  asset: "line-1",
217
- objectType: "energy-resource",
218
- objectId: "main",
219
- attributes: {
220
- attribute: "measurements",
221
- description: "Metering table",
222
- tableColumns: [
223
- {
224
- name: "active_energy_total",
225
- valueType: "number",
226
- presentationKind: "counter",
227
- defaultAggregation: "last",
228
- counterResetPolicy: "new-value",
229
- },
230
- ],
231
- table: {
232
- time: new Date().toISOString(),
233
- dataGroup: "metering",
234
- columns: [
235
- { name: "active_energy_total", type: "double", value: 12345.6, uom: "kWh" },
236
- { name: "power", type: "double", value: 42.1, uom: "kW" },
237
- ],
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
+ ],
238
241
  },
239
242
  },
240
243
  });
241
- ```
242
-
243
- `dataGroup` is a storage/routing hint for consumers such as archivers. It is not
244
- part of the UNS identity path and is not the same as `objectType`. For example,
245
- an archiver may persist a `table` packet with `dataGroup: "metering"` into a
246
- separate physical table family while the UNS path still comes from
247
- `topic/asset/objectType/objectId/attribute`.
248
-
249
- ## Sync UNS schema from the controller
250
-
251
- `sync-uns-schema` fetches the canonical UNS dictionary and measurements from the controller REST API and refreshes local JSON files and generated TypeScript artifacts.
252
-
253
- ```bash
254
- # Run inside a generated microservice project:
255
- pnpm run sync-uns-schema
256
-
257
- # The controller URL is read from config.json (uns.rest) automatically.
258
- # You will be prompted for the bearer token if not set via env var.
259
- ```
260
-
261
- Or with explicit options:
262
-
263
- ```bash
264
- pnpm run sync-uns-schema --controller-url http://localhost:3200 --token <bearer-token>
265
- ```
266
-
267
- **What it updates (inside a generated project):**
268
- - `uns-dictionary.json`
269
- - `uns-measurements.json`
270
- - `src/uns/uns-dictionary.generated.ts`
271
- - `src/uns/uns-measurements.generated.ts`
272
-
273
- **Options:**
274
-
275
- | Flag | Default | Description |
276
- |---|---|---|
277
- | `--controller-url` | from `config.json` `uns.rest` or `UNS_CONTROLLER_URL` | Controller base URL |
278
- | `--token` | interactive prompt or `UNS_CONTROLLER_TOKEN` | Admin bearer token |
279
- | `--status` | `active` | Dictionary filter: `active`, `draft`, `deprecated`, `all` |
280
- | `--dry-run` | — | Report changes without writing |
281
- | `--dictionary-only` | — | Skip measurements sync |
282
- | `--measurements-only` | — | Skip dictionary sync |
283
- | `--skip-generate` | — | Skip TS regeneration |
284
- | `--project-root <dir>` | auto-detect | Target project root |
285
-
286
- ## Config schema generation
287
-
288
- Edit `src/config/project.config.extension.ts` inside your project and run:
289
-
290
- ```bash
291
- pnpm run generate-config-schema
292
- ```
293
-
294
- This regenerates `config.schema.json` and `src/config/app-config.ts`, keeping editor completions and runtime types in sync with your config extensions.
295
-
296
- ## Infisical secret resolution
297
-
298
- - Looks for `INFISICAL_TOKEN` / `INFISICAL_PERSONAL_TOKEN`, then `/run/secrets/infisical_token`.
299
- - If unavailable, logs a warning and returns `default` (or `undefined` for `optional: true` secrets).
300
- - Required secrets throw with the original error message when Infisical is unreachable.
301
- - Call `resolveInfisicalConfig()` to inspect the resolved token/projectId/siteUrl.
302
-
303
- ## Development
304
-
305
- ```bash
306
- pnpm run typecheck # type-check sources
307
- pnpm run build # emit JS + declarations to dist/
308
- ```
309
-
310
- ## License
311
-
312
- MIT © Aljoša Vister
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
+
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
+
267
+ If the bounded queue is full, `publishMessage()` / `publishMqttMessage()` reject immediately. Use `await proxy.flush()` or `await proxy.drainPublishes()` before shutdown or before assuming all accepted messages have reached the broker. `await proxy.stop()` drains by default with a timeout; use `await proxy.stop({ drain: false })` only when dropping queued messages is acceptable.
268
+
269
+ ## Sync UNS schema from the controller
270
+
271
+ `sync-uns-schema` fetches the canonical UNS dictionary and measurements from the controller REST API and refreshes local JSON files and generated TypeScript artifacts.
272
+
273
+ ```bash
274
+ # Run inside a generated microservice project:
275
+ pnpm run sync-uns-schema
276
+
277
+ # The controller URL is read from config.json (uns.rest) automatically.
278
+ # You will be prompted for the bearer token if not set via env var.
279
+ ```
280
+
281
+ Or with explicit options:
282
+
283
+ ```bash
284
+ pnpm run sync-uns-schema --controller-url http://localhost:3200 --token <bearer-token>
285
+ ```
286
+
287
+ **What it updates (inside a generated project):**
288
+ - `uns-dictionary.json`
289
+ - `uns-measurements.json`
290
+ - `src/uns/uns-dictionary.generated.ts`
291
+ - `src/uns/uns-measurements.generated.ts`
292
+
293
+ **Options:**
294
+
295
+ | Flag | Default | Description |
296
+ |---|---|---|
297
+ | `--controller-url` | from `config.json` `uns.rest` or `UNS_CONTROLLER_URL` | Controller base URL |
298
+ | `--token` | interactive prompt or `UNS_CONTROLLER_TOKEN` | Admin bearer token |
299
+ | `--status` | `active` | Dictionary filter: `active`, `draft`, `deprecated`, `all` |
300
+ | `--dry-run` | — | Report changes without writing |
301
+ | `--dictionary-only` | — | Skip measurements sync |
302
+ | `--measurements-only` | — | Skip dictionary sync |
303
+ | `--skip-generate` | — | Skip TS regeneration |
304
+ | `--project-root <dir>` | auto-detect | Target project root |
305
+
306
+ ## Config schema generation
307
+
308
+ Edit `src/config/project.config.extension.ts` inside your project and run:
309
+
310
+ ```bash
311
+ pnpm run generate-config-schema
312
+ ```
313
+
314
+ This regenerates `config.schema.json` and `src/config/app-config.ts`, keeping editor completions and runtime types in sync with your config extensions.
315
+
316
+ ## Infisical secret resolution
317
+
318
+ - Looks for `INFISICAL_TOKEN` / `INFISICAL_PERSONAL_TOKEN`, then `/run/secrets/infisical_token`.
319
+ - If unavailable, logs a warning and returns `default` (or `undefined` for `optional: true` secrets).
320
+ - Required secrets throw with the original error message when Infisical is unreachable.
321
+ - Call `resolveInfisicalConfig()` to inspect the resolved token/projectId/siteUrl.
322
+
323
+ ## Development
324
+
325
+ ```bash
326
+ pnpm run typecheck # type-check sources
327
+ pnpm run build # emit JS + declarations to dist/
328
+ ```
329
+
330
+ ## License
331
+
332
+ MIT © Aljoša Vister
@@ -1 +1 @@
1
- {"version":3,"file":"base-path.js","sourceRoot":"","sources":["../src/base-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEhE,MAAM,uBAAuB,GAAG,oBAAsF,CAAC;AAEvH,MAAM,YAAY,GAAG,CAAC,KAAa,EAAsB,EAAE;IACzD,IAAI,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAE7B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;YAC9C,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,SAAkB,EAAsB,EAAE;IAClE,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEjC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAErC,IAAI,OAAO,uBAAuB,KAAK,UAAU,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,uBAAuB,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;YAC3D,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;YAClD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;AAC9C,CAAC,CAAC;AAQF,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IAClE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAErG,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,IAAI,SAAS,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAE3E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC","sourcesContent":["import { fileURLToPath } from \"url\";\nimport { dirname, resolve, join } from \"path\";\nimport { existsSync } from \"fs\";\nimport { packageDirectorySync } from \"pkg-dir\";\n\nconst moduleDirectory = dirname(fileURLToPath(import.meta.url));\n\nconst packageDirectorySyncAny = packageDirectorySync as unknown as ((arg?: unknown) => string | undefined) | undefined;\n\nconst fallbackFind = (start: string): string | undefined => {\n let current = resolve(start);\n\n while (true) {\n if (existsSync(join(current, \"package.json\"))) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return undefined;\n }\n\n current = parent;\n }\n};\n\nconst resolveCandidate = (candidate?: string): string | undefined => {\n if (!candidate) return undefined;\n\n const directory = resolve(candidate);\n\n if (typeof packageDirectorySyncAny === \"function\") {\n try {\n const result = packageDirectorySyncAny({ cwd: directory });\n if (typeof result === \"string\") {\n return result;\n }\n } catch {\n // ignore – fall back to alternate invocation style\n }\n\n try {\n const result = packageDirectorySyncAny(directory);\n if (typeof result === \"string\") {\n return result;\n }\n } catch {\n // ignore – fall back to manual search\n }\n }\n\n return fallbackFind(directory) ?? directory;\n};\n\nexport interface ResolveBasePathOptions {\n start?: string;\n envBasePath?: string | null;\n cwd?: string;\n}\n\nexport function resolveBasePath(options: ResolveBasePathOptions = {}): string {\n const { start, envBasePath = process.env.UNS_BASE_PATH ?? undefined, cwd = process.cwd() } = options;\n\n const candidates = [start, envBasePath ?? undefined, cwd, moduleDirectory];\n\n for (const candidate of candidates) {\n const resolved = resolveCandidate(candidate);\n if (resolved) {\n return resolved;\n }\n }\n\n return resolve(moduleDirectory, \"..\");\n}\n\nexport const basePath = resolveBasePath();\n"]}
1
+ {"version":3,"file":"base-path.js","sourceRoot":"","sources":["../src/base-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEhE,MAAM,uBAAuB,GAAG,oBAAsF,CAAC;AAEvH,MAAM,YAAY,GAAG,CAAC,KAAa,EAAsB,EAAE;IACzD,IAAI,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAE7B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;YAC9C,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,SAAkB,EAAsB,EAAE;IAClE,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEjC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAErC,IAAI,OAAO,uBAAuB,KAAK,UAAU,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,uBAAuB,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;YAC3D,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;YAClD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;AAC9C,CAAC,CAAC;AAQF,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IAClE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAErG,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,IAAI,SAAS,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAE3E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC","sourcesContent":["import { fileURLToPath } from \"url\";\r\nimport { dirname, resolve, join } from \"path\";\r\nimport { existsSync } from \"fs\";\r\nimport { packageDirectorySync } from \"pkg-dir\";\r\n\r\nconst moduleDirectory = dirname(fileURLToPath(import.meta.url));\r\n\r\nconst packageDirectorySyncAny = packageDirectorySync as unknown as ((arg?: unknown) => string | undefined) | undefined;\r\n\r\nconst fallbackFind = (start: string): string | undefined => {\r\n let current = resolve(start);\r\n\r\n while (true) {\r\n if (existsSync(join(current, \"package.json\"))) {\r\n return current;\r\n }\r\n\r\n const parent = dirname(current);\r\n if (parent === current) {\r\n return undefined;\r\n }\r\n\r\n current = parent;\r\n }\r\n};\r\n\r\nconst resolveCandidate = (candidate?: string): string | undefined => {\r\n if (!candidate) return undefined;\r\n\r\n const directory = resolve(candidate);\r\n\r\n if (typeof packageDirectorySyncAny === \"function\") {\r\n try {\r\n const result = packageDirectorySyncAny({ cwd: directory });\r\n if (typeof result === \"string\") {\r\n return result;\r\n }\r\n } catch {\r\n // ignore – fall back to alternate invocation style\r\n }\r\n\r\n try {\r\n const result = packageDirectorySyncAny(directory);\r\n if (typeof result === \"string\") {\r\n return result;\r\n }\r\n } catch {\r\n // ignore – fall back to manual search\r\n }\r\n }\r\n\r\n return fallbackFind(directory) ?? directory;\r\n};\r\n\r\nexport interface ResolveBasePathOptions {\r\n start?: string;\r\n envBasePath?: string | null;\r\n cwd?: string;\r\n}\r\n\r\nexport function resolveBasePath(options: ResolveBasePathOptions = {}): string {\r\n const { start, envBasePath = process.env.UNS_BASE_PATH ?? undefined, cwd = process.cwd() } = options;\r\n\r\n const candidates = [start, envBasePath ?? undefined, cwd, moduleDirectory];\r\n\r\n for (const candidate of candidates) {\r\n const resolved = resolveCandidate(candidate);\r\n if (resolved) {\r\n return resolved;\r\n }\r\n }\r\n\r\n return resolve(moduleDirectory, \"..\");\r\n}\r\n\r\nexport const basePath = resolveBasePath();\r\n"]}