@uns-kit/core 3.0.46 → 3.0.48

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 (35) hide show
  1. package/MIGRATIONS.md +29 -0
  2. package/README.md +24 -22
  3. package/dist/runtime/catalog-publication-client.d.ts +21 -0
  4. package/dist/runtime/catalog-publication-client.d.ts.map +1 -0
  5. package/dist/runtime/catalog-publication-client.js +92 -0
  6. package/dist/runtime/catalog-publication-client.js.map +1 -0
  7. package/dist/runtime/catalog-publication.d.ts +97 -0
  8. package/dist/runtime/catalog-publication.d.ts.map +1 -0
  9. package/dist/runtime/catalog-publication.js +185 -0
  10. package/dist/runtime/catalog-publication.js.map +1 -0
  11. package/dist/runtime/service-access-examples/README.md +20 -0
  12. package/dist/runtime/service-access-examples/background-reader/README.md +25 -0
  13. package/dist/runtime/service-access-examples/background-reader/build.mjs +7 -0
  14. package/dist/runtime/service-access-examples/background-reader/config-example.json +22 -0
  15. package/dist/runtime/service-access-examples/background-reader/package.json +28 -0
  16. package/dist/runtime/service-access-examples/background-reader/service-access.json +11 -0
  17. package/dist/runtime/service-access-examples/background-reader/src/index.mjs +55 -0
  18. package/dist/runtime/service-access-examples/background-reader/src/reader.mjs +21 -0
  19. package/dist/runtime/service-access-examples/catalog-summary/README.md +26 -0
  20. package/dist/runtime/service-access-examples/catalog-summary/build.mjs +7 -0
  21. package/dist/runtime/service-access-examples/catalog-summary/config-example.json +23 -0
  22. package/dist/runtime/service-access-examples/catalog-summary/package.json +29 -0
  23. package/dist/runtime/service-access-examples/catalog-summary/service-access.json +65 -0
  24. package/dist/runtime/service-access-examples/catalog-summary/src/index.mjs +50 -0
  25. package/dist/runtime/service-access-examples/catalog-summary/src/offer.mjs +35 -0
  26. package/dist/runtime/service-access-guide.md +246 -0
  27. package/dist/runtime/service-access.d.ts +23 -0
  28. package/dist/runtime/service-access.d.ts.map +1 -0
  29. package/dist/runtime/service-access.js +62 -0
  30. package/dist/runtime/service-access.js.map +1 -0
  31. package/dist/runtime/service-access.test.d.ts +2 -0
  32. package/dist/runtime/service-access.test.d.ts.map +1 -0
  33. package/dist/runtime/service-access.test.js +57 -0
  34. package/dist/runtime/service-access.test.js.map +1 -0
  35. package/package.json +14 -1
package/MIGRATIONS.md CHANGED
@@ -9,6 +9,35 @@ Agents must inspect the application's existing ownership and shutdown flow
9
9
  before editing it. The examples below describe the intended behavior, not a
10
10
  mechanical search-and-replace operation.
11
11
 
12
+ ## 3.0.48 - Checked Service access documentation and examples
13
+
14
+ This patch clarifies existing 3.0.47 contracts; no new permission mode or automatic
15
+ application migration is introduced. Follow the
16
+ [source walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md)
17
+ or bundled `@uns-kit/core/runtime/service-access-guide.md` and complete projects in
18
+ `dist/runtime/service-access-examples/`. Examples use a rotating token provider;
19
+ background API reads, caller-mediated catalogs and MQTT ACLs are separate.
20
+ An application adopting the capability must still follow the 3.0.47 entry below.
21
+
22
+ ## 3.0.47 - Common Service access and publications
23
+
24
+ When crossing `<3.0.47` to `>=3.0.47`, adoption is explicit; existing endpoint authorization and caller forwarding do
25
+ not change automatically. Ship `service-access.json` referenced by
26
+ `package.json` `unsDatahub.serviceAccess`, retaining the data file in the build.
27
+ Use `parseServiceAccessDeclaration` and review the exact installed release in
28
+ Controller Service access. Background services need no Catalog/API. Optional
29
+ `publications` reuse the catalog manifest, `CatalogPublicationClient`, and API
30
+ `catalogPublicationHandler`; call `api.registerCatalogRoute()` for signed launch
31
+ route publication. Source approval remains separate from raw workload rights.
32
+ The unpublished earlier `catalogAccess` marker is a compatibility reader only;
33
+ never declare both. Read the [bundled Service access guide](dist/runtime/service-access-guide.md).
34
+
35
+ Do not remove existing credentials until the new path passes deployed
36
+ acceptance. The matching Controller capability must be installed and reviewed
37
+ before enabling the declaration. SDK publication and consumer frozen-lock
38
+ updates remain separate steps. This bounded adapter covers UNS history; it
39
+ does not create SQL, broker or arbitrary external-resource permissions.
40
+
12
41
  ## 3.0.46 - Verified retained-store copy and recovery
13
42
 
14
43
  When crossing `<3.0.46` to `>=3.0.46`, existing rename and unbound consumers
package/README.md CHANGED
@@ -8,10 +8,10 @@ Note: Apps built with uns-kit are intended to be managed by the **UNS OpenHub co
8
8
 
9
9
  | Package | Description |
10
10
  | --- | --- |
11
- | [`@uns-kit/core`](https://github.com/uns-openhub/uns-kit/tree/main/packages/uns-core) | Base runtime (UnsProxyProcess, MQTT helpers, config tooling, gRPC gateway). |
12
- | [`@uns-kit/api`](https://github.com/uns-openhub/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-openhub/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-openhub/uns-kit/tree/main/packages/uns-cli) | CLI for scaffolding new UNS applications. |
11
+ | [`@uns-kit/core`](https://github.com/uns-openhub/uns-kit/tree/master/packages/uns-core) | Base runtime (UnsProxyProcess, MQTT helpers, config tooling, gRPC gateway). |
12
+ | [`@uns-kit/api`](https://github.com/uns-openhub/uns-kit/tree/master/packages/uns-api) | Express plugin — HTTP endpoints, JWT/JWKS auth, Swagger, UNS metadata. |
13
+ | [`@uns-kit/cron`](https://github.com/uns-openhub/uns-kit/tree/master/packages/uns-cron) | Cron-driven scheduler that emits UNS events on a fixed cadence. |
14
+ | [`@uns-kit/cli`](https://github.com/uns-openhub/uns-kit/tree/master/packages/uns-cli) | CLI for scaffolding new UNS applications. |
15
15
 
16
16
  ## Installation
17
17
 
@@ -95,47 +95,49 @@ const cron = await proc.createCrontabProxy("*/5 * * * *", { event: "tick" });
95
95
 
96
96
  ## Datahub client (last value + history)
97
97
 
98
- `UnsClient` provides a minimal REST client for the UNS OpenHub 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.
98
+ `UnsClient` provides a minimal REST client for the UNS OpenHub API, including batch last-value, single-topic catch-all history, and batch range endpoints. Use `ServiceTokenProvider` for Controller-managed services so credential rotation is picked up on every request. Review the service's declared scopes and resource paths before Start; never cache a managed token at application startup.
99
99
 
100
- ```ts
101
- import { UnsClient } from "@uns-kit/core";
102
- import { ConfigFile } from "@uns-kit/core";
100
+ Read the [source Service access walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md)
101
+ or bundled `@uns-kit/core/runtime/service-access-guide.md`. Complete background-reader
102
+ and catalog projects ship in `dist/runtime/service-access-examples/`. A background
103
+ reader uses its service identity only; a catalog operation also carries its caller's
104
+ identity. These API permissions do not create MQTT ACLs or SQL grants.
103
105
 
104
- const config = await ConfigFile.loadConfig();
106
+ ```ts
107
+ import { ServiceTokenProvider, UnsClient } from "@uns-kit/core";
105
108
 
106
- const client = new UnsClient("https://datahub.example.com", {
107
- token: process.env.UNS_SERVICE_TOKEN ?? config.uns.token,
108
- });
109
+ const baseUrl = process.env.UNS_CONTROLLER_PUBLIC_BASE;
110
+ if (!baseUrl) throw new Error("Set the trusted Controller base URL.");
111
+ const client = new UnsClient(baseUrl, { tokenProvider: new ServiceTokenProvider() });
109
112
 
110
113
  const values = await client.lastValue([
111
- "raw/data/line-1/motor/main/temperature",
112
- "raw/data/line-1/motor/main/status",
114
+ "example/plant/line-1/motor/main/temperature",
115
+ "example/plant/line-1/motor/main/status",
113
116
  ]);
114
- console.log(values);
117
+ // Process the values without logging private records.
115
118
 
116
- const data = await client.getAttributeData("sij/acroni/vv/hrm-furnace/equipment/pusher/output-quantity", {
119
+ const data = await client.getAttributeData("example/plant/line-1/motor/main/temperature", {
117
120
  from: "2026-05-07T11:17:01.157Z",
118
121
  to: "2026-05-07T11:22:01.157Z",
119
- table: "uns_sij_hrm_furnace_data",
120
122
  aggregate: "last",
121
123
  dedupe: false,
122
124
  });
123
- console.log(data?.toRecords());
125
+ const records = data?.toRecords();
124
126
 
125
127
  const customData = await client.getData("/projects/project-name/path-to-data/data", {
126
128
  fromDate: "20260325",
127
129
  });
128
- console.log(await customData.json());
130
+ const customResult = await customData.json();
129
131
 
130
132
  const batchHistory = await client.history([
131
- "sij/acroni/vv/hrm-furnace/equipment/zone-1/temperature",
132
- "sij/acroni/vv/hrm-furnace/equipment/zone-2/temperature",
133
+ "example/plant/line-1/motor/main/temperature",
134
+ "example/plant/line-1/motor/main/status",
133
135
  ], {
134
136
  from: "2026-04-09T06:00:00Z",
135
137
  to: "2026-04-09T07:00:00Z",
136
138
  limit: 500,
137
139
  });
138
- console.log(batchHistory?.byTopic);
140
+ // Process the bounded batch result.
139
141
  ```
140
142
 
141
143
  ## Sub-Asset Publishing
@@ -0,0 +1,21 @@
1
+ import { type AccessTokenProvider } from "../tools/auth/service-token-provider.js";
2
+ import { type CatalogPublicationManifest } from "./catalog-publication.js";
3
+ /** No caller-token fallback and no arbitrary source URL or topic. */
4
+ export declare class CatalogPublicationClient {
5
+ private readonly options;
6
+ constructor(options: {
7
+ controllerBase: string;
8
+ rttNode: string;
9
+ version: string;
10
+ instanceId: string;
11
+ manifest: CatalogPublicationManifest;
12
+ tokenProvider?: AccessTokenProvider;
13
+ fetch?: typeof fetch;
14
+ });
15
+ private call;
16
+ execute<T>(operationId: string, input: Record<string, unknown>, callerToken: string, transform: (context: {
17
+ signal: AbortSignal;
18
+ history: (sourceId: string) => Promise<unknown>;
19
+ }) => Promise<T>): Promise<T>;
20
+ }
21
+ //# sourceMappingURL=catalog-publication-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication-client.d.ts","sourceRoot":"","sources":["../../src/runtime/catalog-publication-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAwB,MAAM,yCAAyC,CAAC;AACzG,OAAO,EAIL,KAAK,0BAA0B,EAIhC,MAAM,0BAA0B,CAAC;AAElC,qEAAqE;AACrE,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,0BAA0B,CAAC;QACrC,aAAa,CAAC,EAAE,mBAAmB,CAAC;QACpC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;KACtB;YAEW,IAAI;IAmDZ,OAAO,CAAC,CAAC,EACb,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3G,OAAO,CAAC,CAAC,CAAC;CA6Bd"}
@@ -0,0 +1,92 @@
1
+ import { ServiceTokenProvider } from "../tools/auth/service-token-provider.js";
2
+ import { CatalogAccessError, assertCatalogRequest, assertCatalogResponse, catalogAccessDigest, } from "./catalog-publication.js";
3
+ /** No caller-token fallback and no arbitrary source URL or topic. */
4
+ export class CatalogPublicationClient {
5
+ options;
6
+ constructor(options) {
7
+ this.options = options;
8
+ }
9
+ async call(action, operation, input, callerToken, sourceId, signal) {
10
+ assertCatalogRequest(operation, input);
11
+ const token = await (this.options.tokenProvider ?? new ServiceTokenProvider()).getAccessToken();
12
+ if (!token)
13
+ throw Error("Catalog service credential unavailable.");
14
+ const body = {
15
+ rttNode: this.options.rttNode,
16
+ version: this.options.version,
17
+ instanceId: this.options.instanceId,
18
+ operationId: operation.id,
19
+ manifestDigest: catalogAccessDigest(this.options.manifest),
20
+ input,
21
+ callerToken,
22
+ };
23
+ const response = await (this.options.fetch ?? fetch)(new URL(`/api/catalog-publications/${action}`, this.options.controllerBase), {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
26
+ body: JSON.stringify({ ...body, ...(sourceId ? { sourceId } : {}) }),
27
+ signal: signal ?? AbortSignal.timeout(operation.timeoutMs),
28
+ redirect: "error",
29
+ });
30
+ if (!response.ok)
31
+ throw new CatalogAccessError("catalog_access_denied", [400, 401, 403, 409, 429].includes(response.status) ? response.status : 503);
32
+ if (!response.body)
33
+ throw new CatalogAccessError("catalog_source_unavailable", 503);
34
+ const reader = response.body.getReader();
35
+ let size = 0, text = "";
36
+ const decoder = new TextDecoder();
37
+ try {
38
+ while (true) {
39
+ const r = await reader.read();
40
+ if (r.done)
41
+ break;
42
+ size += r.value.byteLength;
43
+ if (size > 1048576) {
44
+ await reader.cancel();
45
+ throw new CatalogAccessError("catalog_source_budget", 503);
46
+ }
47
+ text += decoder.decode(r.value, { stream: true });
48
+ }
49
+ text += decoder.decode();
50
+ return JSON.parse(text);
51
+ }
52
+ finally {
53
+ reader.releaseLock();
54
+ }
55
+ }
56
+ async execute(operationId, input, callerToken, transform) {
57
+ const operation = this.options.manifest.operations.find((op) => op.id === operationId);
58
+ if (!operation)
59
+ throw Error("Catalog operation not declared.");
60
+ const signal = AbortSignal.timeout(operation.timeoutMs);
61
+ await this.call("authorize", operation, input, callerToken, undefined, signal);
62
+ const work = transform({
63
+ signal,
64
+ history: async (sourceId) => {
65
+ if (!operation.sources.some((source) => source.id === sourceId))
66
+ throw Error("Catalog source not declared.");
67
+ return this.call("history", operation, input, callerToken, sourceId, signal);
68
+ },
69
+ });
70
+ let stop;
71
+ const deadline = new Promise((_resolve, reject) => {
72
+ stop = () => reject(new CatalogAccessError("catalog_execution_timeout", 504));
73
+ if (signal.aborted)
74
+ stop();
75
+ else
76
+ signal.addEventListener("abort", stop, { once: true });
77
+ });
78
+ let result;
79
+ try {
80
+ result = await Promise.race([work, deadline]);
81
+ }
82
+ finally {
83
+ if (stop)
84
+ signal.removeEventListener("abort", stop);
85
+ }
86
+ // Recheck after asynchronous processing, including a cached application result.
87
+ await this.call("authorize", operation, input, callerToken, undefined, signal);
88
+ assertCatalogResponse(operation, result);
89
+ return result;
90
+ }
91
+ }
92
+ //# sourceMappingURL=catalog-publication-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication-client.js","sourceRoot":"","sources":["../../src/runtime/catalog-publication-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AccessTokenProvider, ServiceTokenProvider } from \"../tools/auth/service-token-provider.js\";\nimport {\n CatalogAccessError,\n type CatalogInvocation,\n type CatalogOperation,\n type CatalogPublicationManifest,\n assertCatalogRequest,\n assertCatalogResponse,\n catalogAccessDigest,\n} from \"./catalog-publication.js\";\n\n/** No caller-token fallback and no arbitrary source URL or topic. */\nexport class CatalogPublicationClient {\n constructor(\n private readonly options: {\n controllerBase: string;\n rttNode: string;\n version: string;\n instanceId: string;\n manifest: CatalogPublicationManifest;\n tokenProvider?: AccessTokenProvider;\n fetch?: typeof fetch;\n },\n ) {}\n private async call(\n action: \"authorize\" | \"history\",\n operation: CatalogOperation,\n input: Record<string, unknown>,\n callerToken: string,\n sourceId?: string,\n signal?: AbortSignal,\n ): Promise<unknown> {\n assertCatalogRequest(operation, input);\n const token = await (this.options.tokenProvider ?? new ServiceTokenProvider()).getAccessToken();\n if (!token) throw Error(\"Catalog service credential unavailable.\");\n const body: CatalogInvocation = {\n rttNode: this.options.rttNode,\n version: this.options.version,\n instanceId: this.options.instanceId,\n operationId: operation.id,\n manifestDigest: catalogAccessDigest(this.options.manifest),\n input,\n callerToken,\n };\n const response = await (this.options.fetch ?? fetch)(new URL(`/api/catalog-publications/${action}`, this.options.controllerBase), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${token}` },\n body: JSON.stringify({ ...body, ...(sourceId ? { sourceId } : {}) }),\n signal: signal ?? AbortSignal.timeout(operation.timeoutMs),\n redirect: \"error\",\n });\n if (!response.ok)\n throw new CatalogAccessError(\"catalog_access_denied\", [400, 401, 403, 409, 429].includes(response.status) ? response.status : 503);\n if (!response.body) throw new CatalogAccessError(\"catalog_source_unavailable\", 503);\n const reader = response.body.getReader();\n let size = 0,\n text = \"\";\n const decoder = new TextDecoder();\n try {\n while (true) {\n const r = await reader.read();\n if (r.done) break;\n size += r.value.byteLength;\n if (size > 1048576) {\n await reader.cancel();\n throw new CatalogAccessError(\"catalog_source_budget\", 503);\n }\n text += decoder.decode(r.value, { stream: true });\n }\n text += decoder.decode();\n return JSON.parse(text);\n } finally {\n reader.releaseLock();\n }\n }\n async execute<T>(\n operationId: string,\n input: Record<string, unknown>,\n callerToken: string,\n transform: (context: { signal: AbortSignal; history: (sourceId: string) => Promise<unknown> }) => Promise<T>,\n ): Promise<T> {\n const operation = this.options.manifest.operations.find((op) => op.id === operationId);\n if (!operation) throw Error(\"Catalog operation not declared.\");\n const signal = AbortSignal.timeout(operation.timeoutMs);\n await this.call(\"authorize\", operation, input, callerToken, undefined, signal);\n const work = transform({\n signal,\n history: async (sourceId) => {\n if (!operation.sources.some((source) => source.id === sourceId)) throw Error(\"Catalog source not declared.\");\n return this.call(\"history\", operation, input, callerToken, sourceId, signal);\n },\n });\n let stop: (() => void) | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n stop = () => reject(new CatalogAccessError(\"catalog_execution_timeout\", 504));\n if (signal.aborted) stop();\n else signal.addEventListener(\"abort\", stop, { once: true });\n });\n let result: T;\n try {\n result = await Promise.race([work, deadline]);\n } finally {\n if (stop) signal.removeEventListener(\"abort\", stop);\n }\n // Recheck after asynchronous processing, including a cached application result.\n await this.call(\"authorize\", operation, input, callerToken, undefined, signal);\n assertCatalogResponse(operation, result);\n return result;\n }\n}\n"]}
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ export declare const CATALOG_PUBLICATION_PROTOCOL = 1;
3
+ export declare const CATALOG_SOURCE_SCOPE = "catalog:sources:read";
4
+ export declare const CATALOG_MANIFEST_FIELD = "catalogAccess";
5
+ export declare class CatalogAccessError extends Error {
6
+ readonly code: string;
7
+ readonly status: number;
8
+ constructor(code: string, status?: number);
9
+ }
10
+ declare const historySource: z.ZodObject<{
11
+ id: z.ZodString;
12
+ kind: z.ZodLiteral<"history">;
13
+ topic: z.ZodString;
14
+ maxWindowMs: z.ZodDefault<z.ZodNumber>;
15
+ maxRows: z.ZodDefault<z.ZodNumber>;
16
+ fromParameter: z.ZodDefault<z.ZodString>;
17
+ toParameter: z.ZodDefault<z.ZodString>;
18
+ }, z.core.$strict>;
19
+ declare const operation: z.ZodObject<{
20
+ id: z.ZodString;
21
+ title: z.ZodString;
22
+ method: z.ZodLiteral<"GET">;
23
+ path: z.ZodString;
24
+ mode: z.ZodEnum<{
25
+ caller: "caller";
26
+ "approved-service": "approved-service";
27
+ }>;
28
+ requestSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
29
+ responseSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
30
+ sources: z.ZodArray<z.ZodObject<{
31
+ id: z.ZodString;
32
+ kind: z.ZodLiteral<"history">;
33
+ topic: z.ZodString;
34
+ maxWindowMs: z.ZodDefault<z.ZodNumber>;
35
+ maxRows: z.ZodDefault<z.ZodNumber>;
36
+ fromParameter: z.ZodDefault<z.ZodString>;
37
+ toParameter: z.ZodDefault<z.ZodString>;
38
+ }, z.core.$strict>>;
39
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
40
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
41
+ }, z.core.$strict>;
42
+ declare const manifest: z.ZodObject<{
43
+ protocol: z.ZodLiteral<1>;
44
+ operations: z.ZodArray<z.ZodObject<{
45
+ id: z.ZodString;
46
+ title: z.ZodString;
47
+ method: z.ZodLiteral<"GET">;
48
+ path: z.ZodString;
49
+ mode: z.ZodEnum<{
50
+ caller: "caller";
51
+ "approved-service": "approved-service";
52
+ }>;
53
+ requestSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
54
+ responseSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
55
+ sources: z.ZodArray<z.ZodObject<{
56
+ id: z.ZodString;
57
+ kind: z.ZodLiteral<"history">;
58
+ topic: z.ZodString;
59
+ maxWindowMs: z.ZodDefault<z.ZodNumber>;
60
+ maxRows: z.ZodDefault<z.ZodNumber>;
61
+ fromParameter: z.ZodDefault<z.ZodString>;
62
+ toParameter: z.ZodDefault<z.ZodString>;
63
+ }, z.core.$strict>>;
64
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
65
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
66
+ }, z.core.$strict>>;
67
+ }, z.core.$strict>;
68
+ export type CatalogOperationInput = z.input<typeof operation>;
69
+ export type CatalogOperation = z.output<typeof operation>;
70
+ export type CatalogHistorySource = z.output<typeof historySource>;
71
+ export type CatalogPublicationManifest = z.output<typeof manifest>;
72
+ export declare const catalogAccessDigest: (value: unknown) => string;
73
+ /** Catalog routes use the same UNS resource path as SDK endpoint guards. */
74
+ export declare const catalogOperationResource: (path: string) => string;
75
+ /** Inspection consumes bounded JSON data, never application JavaScript. */
76
+ export declare function parseCatalogPublicationManifest(input: unknown): CatalogPublicationManifest;
77
+ export declare function assertCatalogRequest(op: CatalogOperation, input: unknown): void;
78
+ export declare function assertCatalogResponse(op: CatalogOperation, value: unknown): void;
79
+ export declare function catalogHistoryQuery(source: CatalogHistorySource, input: unknown): {
80
+ topics: string[];
81
+ from: string;
82
+ to: string;
83
+ limit: number;
84
+ dedupe: false;
85
+ };
86
+ export declare const catalogInvocationSchema: z.ZodObject<{
87
+ rttNode: z.ZodString;
88
+ version: z.ZodString;
89
+ instanceId: z.ZodString;
90
+ operationId: z.ZodString;
91
+ manifestDigest: z.ZodString;
92
+ input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
93
+ callerToken: z.ZodString;
94
+ }, z.core.$strict>;
95
+ export type CatalogInvocation = z.infer<typeof catalogInvocationSchema>;
96
+ export {};
97
+ //# sourceMappingURL=catalog-publication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication.d.ts","sourceRoot":"","sources":["../../src/runtime/catalog-publication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAC3D,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AAkBtD,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,IAAI,EAAE,MAAM;aACZ,MAAM;gBADN,IAAI,EAAE,MAAM,EACZ,MAAM,SAAM;CAI/B;AACD,QAAA,MAAM,aAAa;;;;;;;;kBAeR,CAAC;AACZ,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;kBAaJ,CAAC;AACZ,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;kBAA+F,CAAC;AAC9G,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAC1D,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAClE,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AACnE,eAAO,MAAM,mBAAmB,4BAAiB,CAAC;AAClD,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MAAsC,CAAC;AAE/F,2EAA2E;AAC3E,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAmC1F;AAiDD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAI/E;AACD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEhF;AACD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,OAAO,GACb;IACD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;CACf,CAWA;AAED,eAAO,MAAM,uBAAuB;;;;;;;;kBAUzB,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -0,0 +1,185 @@
1
+ import { z } from "zod";
2
+ import { operatorDigest, operatorJson, operatorRequestSchema } from "./operator-api.js";
3
+ export const CATALOG_PUBLICATION_PROTOCOL = 1;
4
+ export const CATALOG_SOURCE_SCOPE = "catalog:sources:read";
5
+ export const CATALOG_MANIFEST_FIELD = "catalogAccess";
6
+ const id = z
7
+ .string()
8
+ .min(1)
9
+ .max(120)
10
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);
11
+ const topic = z
12
+ .string()
13
+ .min(1)
14
+ .max(512)
15
+ .regex(/^[A-Za-z0-9-]+(?:\/[A-Za-z0-9-]+)*$/)
16
+ .refine((value) => !value.split("/").some((part) => part === "." || part === ".."));
17
+ const localPath = z
18
+ .string()
19
+ .min(1)
20
+ .max(1024)
21
+ .regex(/^\/api\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_.-]+)*$/)
22
+ .refine((value) => !value.split("/").some((part) => part === "." || part === ".."));
23
+ export class CatalogAccessError extends Error {
24
+ code;
25
+ status;
26
+ constructor(code, status = 400) {
27
+ super(code);
28
+ this.code = code;
29
+ this.status = status;
30
+ }
31
+ }
32
+ const historySource = z
33
+ .object({
34
+ id,
35
+ kind: z.literal("history"),
36
+ topic,
37
+ maxWindowMs: z
38
+ .number()
39
+ .int()
40
+ .min(1000)
41
+ .max(31 * 86400000)
42
+ .default(7 * 86400000),
43
+ maxRows: z.number().int().min(1).max(27000).default(1000),
44
+ fromParameter: id.default("from"),
45
+ toParameter: id.default("to"),
46
+ })
47
+ .strict();
48
+ const operation = z
49
+ .object({
50
+ id,
51
+ title: z.string().min(1).max(160),
52
+ method: z.literal("GET"),
53
+ path: localPath,
54
+ mode: z.enum(["caller", "approved-service"]),
55
+ requestSchema: z.record(z.string(), z.unknown()),
56
+ responseSchema: z.record(z.string(), z.unknown()),
57
+ sources: z.array(historySource).min(1).max(16),
58
+ timeoutMs: z.number().int().min(500).max(30000).default(10000),
59
+ maxResponseBytes: z.number().int().min(256).max(1048576).default(262144),
60
+ })
61
+ .strict();
62
+ const manifest = z.object({ protocol: z.literal(1), operations: z.array(operation).min(1).max(32) }).strict();
63
+ export const catalogAccessDigest = operatorDigest;
64
+ /** Catalog routes use the same UNS resource path as SDK endpoint guards. */
65
+ export const catalogOperationResource = (path) => path.replace(/^\/api\//, "");
66
+ /** Inspection consumes bounded JSON data, never application JavaScript. */
67
+ export function parseCatalogPublicationManifest(input) {
68
+ try {
69
+ operatorJson(input);
70
+ const parsed = manifest.parse(input), ids = new Set(), paths = new Set();
71
+ for (const op of parsed.operations) {
72
+ if (ids.has(op.id) || paths.has(op.path))
73
+ throw Error("duplicate operation");
74
+ ids.add(op.id);
75
+ paths.add(op.path);
76
+ op.requestSchema = catalogSchema(op.requestSchema);
77
+ op.responseSchema = catalogSchema(op.responseSchema);
78
+ if (op.requestSchema["type"] !== "object")
79
+ throw Error("request must be an object");
80
+ if (Object.values(op.requestSchema["properties"] ?? {}).some((p) => p["type"] !== "string"))
81
+ throw Error("GET parameters must be strings");
82
+ const sources = new Set();
83
+ for (const source of op.sources) {
84
+ if (sources.has(source.id) || source.fromParameter === source.toParameter)
85
+ throw Error("source binding");
86
+ sources.add(source.id);
87
+ const properties = op.requestSchema["properties"];
88
+ for (const key of [source.fromParameter, source.toParameter]) {
89
+ const parameter = properties?.[key];
90
+ if (parameter?.["type"] !== "string" ||
91
+ parameter["format"] !== "date-time" ||
92
+ !op.requestSchema["required"]?.includes(key))
93
+ throw Error("time parameter binding");
94
+ }
95
+ }
96
+ }
97
+ return parsed;
98
+ }
99
+ catch {
100
+ throw new CatalogAccessError("catalog_manifest_invalid");
101
+ }
102
+ }
103
+ function catalogSchema(value) {
104
+ const schema = operatorRequestSchema(value);
105
+ const visit = (node) => {
106
+ if (!["object", "array", "string", "integer", "number", "boolean", "null"].includes(String(node["type"])) ||
107
+ ["oneOf", "anyOf", "allOf", "default"].some((key) => key in node))
108
+ throw Error("unsupported catalog schema");
109
+ if (node["properties"])
110
+ for (const child of Object.values(node["properties"]))
111
+ visit(child);
112
+ if (node["items"])
113
+ visit(node["items"]);
114
+ };
115
+ visit(schema);
116
+ return schema;
117
+ }
118
+ /** Zod's JSON Schema importer limits date-time to UTC. JSON Schema permits
119
+ * explicit offsets. Check these formats separately without altering input. */
120
+ function offsetSchema(schema, value) {
121
+ const copy = { ...schema };
122
+ if (copy["format"] === "date-time") {
123
+ if (value !== undefined && !z.iso.datetime({ offset: true }).safeParse(value).success)
124
+ throw Error("date-time");
125
+ delete copy["format"];
126
+ }
127
+ if (copy["properties"])
128
+ copy["properties"] = Object.fromEntries(Object.entries(copy["properties"]).map(([key, child]) => [
129
+ key,
130
+ offsetSchema(child, value && typeof value === "object" ? value[key] : undefined),
131
+ ]));
132
+ if (copy["items"]) {
133
+ const child = copy["items"];
134
+ if (Array.isArray(value))
135
+ for (const item of value)
136
+ offsetSchema(child, item);
137
+ copy["items"] = offsetSchema(child, undefined);
138
+ }
139
+ return copy;
140
+ }
141
+ function assertSchema(schema, value, code, maxBytes) {
142
+ try {
143
+ const json = JSON.stringify(value);
144
+ if (json === undefined || Buffer.byteLength(json, "utf8") > maxBytes)
145
+ throw Error("size");
146
+ // fromJSONSchema strips unknown keys by default, so validate the unchanged
147
+ // value against a strict schema rather than returning parsed/transformed data.
148
+ if (!z.fromJSONSchema(offsetSchema(schema, value)).safeParse(value).success)
149
+ throw Error("schema");
150
+ }
151
+ catch {
152
+ throw new CatalogAccessError(code, code === "catalog_response_invalid" ? 502 : 400);
153
+ }
154
+ }
155
+ export function assertCatalogRequest(op, input) {
156
+ operatorJson(input);
157
+ assertSchema(op.requestSchema, input, "catalog_request_invalid", 65536);
158
+ for (const source of op.sources)
159
+ catalogHistoryQuery(source, input);
160
+ }
161
+ export function assertCatalogResponse(op, value) {
162
+ assertSchema(op.responseSchema, value, "catalog_response_invalid", op.maxResponseBytes);
163
+ }
164
+ export function catalogHistoryQuery(source, input) {
165
+ const values = input;
166
+ const from = values?.[source.fromParameter], to = values?.[source.toParameter];
167
+ if (!z.iso.datetime({ offset: true }).safeParse(from).success || !z.iso.datetime({ offset: true }).safeParse(to).success)
168
+ throw new CatalogAccessError("catalog_window_invalid");
169
+ const start = Date.parse(from), end = Date.parse(to);
170
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || end - start > source.maxWindowMs)
171
+ throw new CatalogAccessError("catalog_window_invalid");
172
+ return { topics: [source.topic], from: new Date(start).toISOString(), to: new Date(end).toISOString(), limit: source.maxRows, dedupe: false };
173
+ }
174
+ export const catalogInvocationSchema = z
175
+ .object({
176
+ rttNode: id,
177
+ version: id,
178
+ instanceId: id,
179
+ operationId: id,
180
+ manifestDigest: z.string().regex(/^[a-f0-9]{64}$/),
181
+ input: z.record(z.string(), z.unknown()),
182
+ callerToken: z.string().min(1).max(16384),
183
+ })
184
+ .strict();
185
+ //# sourceMappingURL=catalog-publication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication.js","sourceRoot":"","sources":["../../src/runtime/catalog-publication.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { operatorDigest, operatorJson, operatorRequestSchema } from \"./operator-api.js\";\n\nexport const CATALOG_PUBLICATION_PROTOCOL = 1;\nexport const CATALOG_SOURCE_SCOPE = \"catalog:sources:read\";\nexport const CATALOG_MANIFEST_FIELD = \"catalogAccess\";\nconst id = z\n .string()\n .min(1)\n .max(120)\n .regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);\nconst topic = z\n .string()\n .min(1)\n .max(512)\n .regex(/^[A-Za-z0-9-]+(?:\\/[A-Za-z0-9-]+)*$/)\n .refine((value) => !value.split(\"/\").some((part) => part === \".\" || part === \"..\"));\nconst localPath = z\n .string()\n .min(1)\n .max(1024)\n .regex(/^\\/api\\/[A-Za-z0-9_-]+(?:\\/[A-Za-z0-9_.-]+)*$/)\n .refine((value) => !value.split(\"/\").some((part) => part === \".\" || part === \"..\"));\nexport class CatalogAccessError extends Error {\n constructor(\n public readonly code: string,\n public readonly status = 400,\n ) {\n super(code);\n }\n}\nconst historySource = z\n .object({\n id,\n kind: z.literal(\"history\"),\n topic,\n maxWindowMs: z\n .number()\n .int()\n .min(1000)\n .max(31 * 86400000)\n .default(7 * 86400000),\n maxRows: z.number().int().min(1).max(27000).default(1000),\n fromParameter: id.default(\"from\"),\n toParameter: id.default(\"to\"),\n })\n .strict();\nconst operation = z\n .object({\n id,\n title: z.string().min(1).max(160),\n method: z.literal(\"GET\"),\n path: localPath,\n mode: z.enum([\"caller\", \"approved-service\"]),\n requestSchema: z.record(z.string(), z.unknown()),\n responseSchema: z.record(z.string(), z.unknown()),\n sources: z.array(historySource).min(1).max(16),\n timeoutMs: z.number().int().min(500).max(30000).default(10000),\n maxResponseBytes: z.number().int().min(256).max(1048576).default(262144),\n })\n .strict();\nconst manifest = z.object({ protocol: z.literal(1), operations: z.array(operation).min(1).max(32) }).strict();\nexport type CatalogOperationInput = z.input<typeof operation>;\nexport type CatalogOperation = z.output<typeof operation>;\nexport type CatalogHistorySource = z.output<typeof historySource>;\nexport type CatalogPublicationManifest = z.output<typeof manifest>;\nexport const catalogAccessDigest = operatorDigest;\n/** Catalog routes use the same UNS resource path as SDK endpoint guards. */\nexport const catalogOperationResource = (path: string): string => path.replace(/^\\/api\\//, \"\");\n\n/** Inspection consumes bounded JSON data, never application JavaScript. */\nexport function parseCatalogPublicationManifest(input: unknown): CatalogPublicationManifest {\n try {\n operatorJson(input);\n const parsed = manifest.parse(input),\n ids = new Set<string>(),\n paths = new Set<string>();\n for (const op of parsed.operations) {\n if (ids.has(op.id) || paths.has(op.path)) throw Error(\"duplicate operation\");\n ids.add(op.id);\n paths.add(op.path);\n op.requestSchema = catalogSchema(op.requestSchema);\n op.responseSchema = catalogSchema(op.responseSchema);\n if (op.requestSchema[\"type\"] !== \"object\") throw Error(\"request must be an object\");\n if (Object.values((op.requestSchema[\"properties\"] as Record<string, Record<string, unknown>>) ?? {}).some((p) => p[\"type\"] !== \"string\"))\n throw Error(\"GET parameters must be strings\");\n const sources = new Set<string>();\n for (const source of op.sources) {\n if (sources.has(source.id) || source.fromParameter === source.toParameter) throw Error(\"source binding\");\n sources.add(source.id);\n const properties = op.requestSchema[\"properties\"] as Record<string, unknown> | undefined;\n for (const key of [source.fromParameter, source.toParameter]) {\n const parameter = properties?.[key] as Record<string, unknown> | undefined;\n if (\n parameter?.[\"type\"] !== \"string\" ||\n parameter[\"format\"] !== \"date-time\" ||\n !(op.requestSchema[\"required\"] as string[] | undefined)?.includes(key)\n )\n throw Error(\"time parameter binding\");\n }\n }\n }\n return parsed;\n } catch {\n throw new CatalogAccessError(\"catalog_manifest_invalid\");\n }\n}\n\nfunction catalogSchema(value: unknown): Record<string, unknown> {\n const schema = operatorRequestSchema(value);\n const visit = (node: Record<string, unknown>): void => {\n if (\n ![\"object\", \"array\", \"string\", \"integer\", \"number\", \"boolean\", \"null\"].includes(String(node[\"type\"])) ||\n [\"oneOf\", \"anyOf\", \"allOf\", \"default\"].some((key) => key in node)\n )\n throw Error(\"unsupported catalog schema\");\n if (node[\"properties\"]) for (const child of Object.values(node[\"properties\"] as Record<string, Record<string, unknown>>)) visit(child);\n if (node[\"items\"]) visit(node[\"items\"] as Record<string, unknown>);\n };\n visit(schema);\n return schema;\n}\n/** Zod's JSON Schema importer limits date-time to UTC. JSON Schema permits\n * explicit offsets. Check these formats separately without altering input. */\nfunction offsetSchema(schema: Record<string, unknown>, value: unknown): Record<string, unknown> {\n const copy = { ...schema };\n if (copy[\"format\"] === \"date-time\") {\n if (value !== undefined && !z.iso.datetime({ offset: true }).safeParse(value).success) throw Error(\"date-time\");\n delete copy[\"format\"];\n }\n if (copy[\"properties\"])\n copy[\"properties\"] = Object.fromEntries(\n Object.entries(copy[\"properties\"] as Record<string, Record<string, unknown>>).map(([key, child]) => [\n key,\n offsetSchema(child, value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined),\n ]),\n );\n if (copy[\"items\"]) {\n const child = copy[\"items\"] as Record<string, unknown>;\n if (Array.isArray(value)) for (const item of value) offsetSchema(child, item);\n copy[\"items\"] = offsetSchema(child, undefined);\n }\n return copy;\n}\nfunction assertSchema(schema: Record<string, unknown>, value: unknown, code: string, maxBytes: number): void {\n try {\n const json = JSON.stringify(value);\n if (json === undefined || Buffer.byteLength(json, \"utf8\") > maxBytes) throw Error(\"size\");\n // fromJSONSchema strips unknown keys by default, so validate the unchanged\n // value against a strict schema rather than returning parsed/transformed data.\n if (!z.fromJSONSchema(offsetSchema(schema, value) as never).safeParse(value).success) throw Error(\"schema\");\n } catch {\n throw new CatalogAccessError(code, code === \"catalog_response_invalid\" ? 502 : 400);\n }\n}\nexport function assertCatalogRequest(op: CatalogOperation, input: unknown): void {\n operatorJson(input);\n assertSchema(op.requestSchema, input, \"catalog_request_invalid\", 65536);\n for (const source of op.sources) catalogHistoryQuery(source, input);\n}\nexport function assertCatalogResponse(op: CatalogOperation, value: unknown): void {\n assertSchema(op.responseSchema, value, \"catalog_response_invalid\", op.maxResponseBytes);\n}\nexport function catalogHistoryQuery(\n source: CatalogHistorySource,\n input: unknown,\n): {\n topics: string[];\n from: string;\n to: string;\n limit: number;\n dedupe: false;\n} {\n const values = input as Record<string, unknown> | null;\n const from = values?.[source.fromParameter],\n to = values?.[source.toParameter];\n if (!z.iso.datetime({ offset: true }).safeParse(from).success || !z.iso.datetime({ offset: true }).safeParse(to).success)\n throw new CatalogAccessError(\"catalog_window_invalid\");\n const start = Date.parse(from as string),\n end = Date.parse(to as string);\n if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || end - start > source.maxWindowMs)\n throw new CatalogAccessError(\"catalog_window_invalid\");\n return { topics: [source.topic], from: new Date(start).toISOString(), to: new Date(end).toISOString(), limit: source.maxRows, dedupe: false };\n}\n\nexport const catalogInvocationSchema = z\n .object({\n rttNode: id,\n version: id,\n instanceId: id,\n operationId: id,\n manifestDigest: z.string().regex(/^[a-f0-9]{64}$/),\n input: z.record(z.string(), z.unknown()),\n callerToken: z.string().min(1).max(16384),\n })\n .strict();\nexport type CatalogInvocation = z.infer<typeof catalogInvocationSchema>;\n"]}
@@ -0,0 +1,20 @@
1
+ # Service access examples
2
+
3
+ - `background-reader/`: no API or catalog; a reviewed workload token reads one UNS history source.
4
+ - `catalog-summary/`: a catalog-only caller requests a bounded derived count; the workload has no direct source permission.
5
+
6
+ Each folder is an independent, credential-free Node.js package. Copy one folder
7
+ to a new project, install dependencies and build. Deploy/review/start with a
8
+ Controller supporting common Service access (verified with Controller 2.2.95).
9
+ The [walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md)
10
+ explains what the developer, administrator, Controller, SDK and caller each own.
11
+
12
+ The examples are also shipped in `@uns-kit/core/dist/runtime/service-access-examples/`.
13
+ No real broker, database, user or production endpoint is supplied. Configure your
14
+ own local test resources. These files create no identities, grants or MQTT ACLs.
15
+
16
+ Maintainers: run `pnpm run ts:build` then `pnpm run test:service-access-examples`.
17
+ The verifier packs the SDK, installs it in a temporary consumer, builds/packs both
18
+ examples, and exercises their real handlers/token provider against a synthetic
19
+ loopback API. This checks imports, shipped files, token rotation, denial and
20
+ closed output, not real Controller policy, MQTT connectivity or cluster ownership.
@@ -0,0 +1,25 @@
1
+ # background-reader
2
+
3
+ A complete Node.js 22+ example using published SDK APIs from 3.0.47 onward.
4
+ Read the [Service access walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md) for the declaration, Controller review and acceptance steps.
5
+
6
+ ```sh
7
+ pnpm install
8
+ pnpm run build
9
+ cp config-example.json config.json
10
+ ```
11
+
12
+ The example configuration targets a local development broker/Controller. Select
13
+ your own endpoints and secret references before deployment. The Controller owns
14
+ instance configuration when deployed. `MQTT_USERNAME`/`MQTT_PASSWORD` concern broker
15
+ ACLs only and grant no OpenHub API rights. Do not commit `config.json` or `.env`.
16
+
17
+ Deploy the package, apply its exact-version Service access policy, and then Start.
18
+ The Controller supplies managed identity/credential paths. Do not manufacture RTT
19
+ launch variables or paste a human token into configuration.
20
+
21
+ This worker has no API or catalog. It polls five minutes of history, at most
22
+ 100 rows, every 30 seconds while active. Provision the exact declared source in
23
+ your own test environment; zero rows is a valid result. Its token can read that
24
+ source, while another source must return 403. SIGINT/SIGTERM aborts the current
25
+ request and stops polling before runtime cleanup.