@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.
- package/MIGRATIONS.md +29 -0
- package/README.md +24 -22
- package/dist/runtime/catalog-publication-client.d.ts +21 -0
- package/dist/runtime/catalog-publication-client.d.ts.map +1 -0
- package/dist/runtime/catalog-publication-client.js +92 -0
- package/dist/runtime/catalog-publication-client.js.map +1 -0
- package/dist/runtime/catalog-publication.d.ts +97 -0
- package/dist/runtime/catalog-publication.d.ts.map +1 -0
- package/dist/runtime/catalog-publication.js +185 -0
- package/dist/runtime/catalog-publication.js.map +1 -0
- package/dist/runtime/service-access-examples/README.md +20 -0
- package/dist/runtime/service-access-examples/background-reader/README.md +25 -0
- package/dist/runtime/service-access-examples/background-reader/build.mjs +7 -0
- package/dist/runtime/service-access-examples/background-reader/config-example.json +22 -0
- package/dist/runtime/service-access-examples/background-reader/package.json +28 -0
- package/dist/runtime/service-access-examples/background-reader/service-access.json +11 -0
- package/dist/runtime/service-access-examples/background-reader/src/index.mjs +55 -0
- package/dist/runtime/service-access-examples/background-reader/src/reader.mjs +21 -0
- package/dist/runtime/service-access-examples/catalog-summary/README.md +26 -0
- package/dist/runtime/service-access-examples/catalog-summary/build.mjs +7 -0
- package/dist/runtime/service-access-examples/catalog-summary/config-example.json +23 -0
- package/dist/runtime/service-access-examples/catalog-summary/package.json +29 -0
- package/dist/runtime/service-access-examples/catalog-summary/service-access.json +65 -0
- package/dist/runtime/service-access-examples/catalog-summary/src/index.mjs +50 -0
- package/dist/runtime/service-access-examples/catalog-summary/src/offer.mjs +35 -0
- package/dist/runtime/service-access-guide.md +246 -0
- package/dist/runtime/service-access.d.ts +23 -0
- package/dist/runtime/service-access.d.ts.map +1 -0
- package/dist/runtime/service-access.js +62 -0
- package/dist/runtime/service-access.js.map +1 -0
- package/dist/runtime/service-access.test.d.ts +2 -0
- package/dist/runtime/service-access.test.d.ts.map +1 -0
- package/dist/runtime/service-access.test.js +57 -0
- package/dist/runtime/service-access.test.js.map +1 -0
- 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/
|
|
12
|
-
| [`@uns-kit/api`](https://github.com/uns-openhub/uns-kit/tree/
|
|
13
|
-
| [`@uns-kit/cron`](https://github.com/uns-openhub/uns-kit/tree/
|
|
14
|
-
| [`@uns-kit/cli`](https://github.com/uns-openhub/uns-kit/tree/
|
|
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.
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
106
|
+
```ts
|
|
107
|
+
import { ServiceTokenProvider, UnsClient } from "@uns-kit/core";
|
|
105
108
|
|
|
106
|
-
const
|
|
107
|
-
|
|
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
|
-
"
|
|
112
|
-
"
|
|
114
|
+
"example/plant/line-1/motor/main/temperature",
|
|
115
|
+
"example/plant/line-1/motor/main/status",
|
|
113
116
|
]);
|
|
114
|
-
|
|
117
|
+
// Process the values without logging private records.
|
|
115
118
|
|
|
116
|
-
const data = await client.getAttributeData("
|
|
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
|
-
|
|
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
|
-
|
|
130
|
+
const customResult = await customData.json();
|
|
129
131
|
|
|
130
132
|
const batchHistory = await client.history([
|
|
131
|
-
"
|
|
132
|
-
"
|
|
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
|
-
|
|
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.
|