@uns-kit/api 2.0.29 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -32
- package/dist/uns-api-proxy.d.ts +14 -2
- package/dist/uns-api-proxy.js +139 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
# @uns-kit/api
|
|
2
2
|
|
|
3
|
-
`@uns-kit/api` exposes Express-based HTTP endpoints for UNS deployments. The plugin attaches a `createApiProxy` method to `UnsProxyProcess`, handles JWT/JWKS access control,
|
|
3
|
+
`@uns-kit/api` exposes Express-based HTTP endpoints for UNS deployments. The plugin attaches a `createApiProxy` method to `UnsProxyProcess`, handles JWT/JWKS access control, automatically publishes API metadata back into the Unified Namespace, and serves a Swagger UI for every registered endpoint.
|
|
4
4
|
|
|
5
5
|
Note: Apps built with uns-kit are intended to be managed by the **UNS Datahub controller**.
|
|
6
6
|
For the MQTT topic registry published by the API plugin, see `../../docs/uns-topics.md`.
|
|
7
7
|
|
|
8
8
|
## uns-kit in context
|
|
9
9
|
|
|
10
|
-
uns-kit is a batteries-included toolkit for Unified Namespace applications. It standardizes MQTT wiring, auth, config schemas, and scaffolding so you can focus on your API surface instead of boilerplate. The toolkit packages are:
|
|
11
|
-
|
|
12
10
|
| Package | Description |
|
|
13
11
|
| --- | --- |
|
|
14
|
-
| [`@uns-kit/core`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-core) | Base runtime
|
|
15
|
-
| [`@uns-kit/api`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-api) | Express plugin
|
|
12
|
+
| [`@uns-kit/core`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-core) | Base runtime (UnsProxyProcess, MQTT helpers, config tooling, gRPC gateway). |
|
|
13
|
+
| [`@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. |
|
|
16
14
|
| [`@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. |
|
|
17
|
-
| [`@uns-kit/
|
|
18
|
-
| [`@uns-kit/cli`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-cli) | Command line tool for scaffolding new UNS applications. |
|
|
15
|
+
| [`@uns-kit/cli`](https://github.com/uns-datahub/uns-kit/tree/main/packages/uns-cli) | CLI for scaffolding new UNS applications. |
|
|
19
16
|
|
|
20
17
|
## Installation
|
|
21
18
|
|
|
@@ -25,43 +22,141 @@ pnpm add @uns-kit/api
|
|
|
25
22
|
npm install @uns-kit/api
|
|
26
23
|
```
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
`@uns-kit/core` must also be present — the plugin augments its `UnsProxyProcess` type.
|
|
29
26
|
|
|
30
|
-
##
|
|
27
|
+
## How it works
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
1. Import `@uns-kit/api` as a side-effect to register the plugin.
|
|
30
|
+
2. Call `process.createApiProxy(instanceName, options)` to start an Express server.
|
|
31
|
+
3. Register endpoints with `api.get(...)` or `api.post(...)`.
|
|
32
|
+
4. Listen to `apiGetEvent` / `apiPostEvent` to handle incoming requests.
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
Every registered endpoint is:
|
|
35
|
+
- Automatically secured with JWT/JWKS or a shared secret.
|
|
36
|
+
- Published to the UNS controller as an API metadata record (topic, host, path, method).
|
|
37
|
+
- Added to the Swagger spec served at `/<processName>/<instanceName>/swagger.json`.
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
## GET endpoint example
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
```ts
|
|
42
|
+
import UnsProxyProcess from "@uns-kit/core/uns/uns-proxy-process";
|
|
43
|
+
import type { UnsEvents } from "@uns-kit/core";
|
|
44
|
+
import "@uns-kit/api";
|
|
45
|
+
import { type UnsProxyProcessWithApi } from "@uns-kit/api";
|
|
46
|
+
|
|
47
|
+
const config = await ConfigFile.loadConfig();
|
|
48
|
+
const proc = new UnsProxyProcess(config.infra.host!, { processName: config.uns.processName }) as UnsProxyProcessWithApi;
|
|
49
|
+
|
|
50
|
+
const api = await proc.createApiProxy("my-service", {
|
|
51
|
+
jwks: { wellKnownJwksUrl: config.uns.jwksWellKnownUrl },
|
|
52
|
+
// or for simple/dev deployments: jwtSecret: "CHANGEME"
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Register a GET endpoint
|
|
56
|
+
// Signature: api.get(topic, asset, objectType, objectId, attribute, options?)
|
|
57
|
+
await api.get(
|
|
58
|
+
"enterprise/site/area/line/",
|
|
59
|
+
"line-3-furnace",
|
|
60
|
+
"energy-resource",
|
|
61
|
+
"main-bus",
|
|
62
|
+
"current",
|
|
63
|
+
{
|
|
64
|
+
tags: ["Energy"],
|
|
65
|
+
apiDescription: "Current reading for line-3-furnace main-bus",
|
|
45
66
|
queryParams: [
|
|
46
|
-
{ name: "from",
|
|
47
|
-
{ name: "to",
|
|
48
|
-
{ name: "limit", type: "number", chatCanonical: "limit", defaultValue:
|
|
67
|
+
{ name: "from", type: "string", required: false, description: "Start of time range (ISO 8601)", chatCanonical: "from" },
|
|
68
|
+
{ name: "to", type: "string", required: false, description: "End of time range (ISO 8601)", chatCanonical: "to" },
|
|
69
|
+
{ name: "limit", type: "number", required: false, description: "Maximum records", chatCanonical: "limit", defaultValue: 100 },
|
|
49
70
|
],
|
|
50
|
-
chatDefaults: {
|
|
51
|
-
|
|
71
|
+
chatDefaults: { limit: 100 },
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Handle incoming GET requests
|
|
76
|
+
api.event.on("apiGetEvent", (event: UnsEvents["apiGetEvent"]) => {
|
|
77
|
+
const { from, to, limit } = event.req.query;
|
|
78
|
+
// fetch your data here
|
|
79
|
+
event.res.json({ status: "ok", data: [] });
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## POST endpoint example
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// Register a POST endpoint
|
|
87
|
+
// Signature: api.post(topic, asset, objectType, objectId, attribute, options?)
|
|
88
|
+
await api.post(
|
|
89
|
+
"enterprise/site/area/line/",
|
|
90
|
+
"line-3-furnace",
|
|
91
|
+
"energy-resource",
|
|
92
|
+
"main-bus",
|
|
93
|
+
"setpoint",
|
|
94
|
+
{
|
|
95
|
+
tags: ["Energy"],
|
|
96
|
+
apiDescription: "Write a new setpoint for line-3-furnace main-bus",
|
|
97
|
+
requestBody: {
|
|
98
|
+
description: "Setpoint payload",
|
|
99
|
+
required: true,
|
|
100
|
+
schema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
required: ["value"],
|
|
103
|
+
properties: {
|
|
104
|
+
value: { type: "number", description: "Target setpoint value" },
|
|
105
|
+
unit: { type: "string", description: "Unit of measurement, e.g. A" },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
52
108
|
},
|
|
53
|
-
}
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Handle incoming POST requests — body is pre-parsed JSON
|
|
113
|
+
api.event.on("apiPostEvent", (event: UnsEvents["apiPostEvent"]) => {
|
|
114
|
+
const { value, unit } = event.req.body;
|
|
115
|
+
// write/command logic here
|
|
116
|
+
event.res.json({ status: "ok", received: { value, unit } });
|
|
117
|
+
});
|
|
118
|
+
```
|
|
54
119
|
|
|
55
|
-
|
|
56
|
-
event.res.json({ status: "ok" });
|
|
57
|
-
});
|
|
58
|
-
}
|
|
120
|
+
## Endpoint signature
|
|
59
121
|
|
|
60
|
-
void main();
|
|
61
122
|
```
|
|
123
|
+
api.get(topic, asset, objectType, objectId, attribute, options?)
|
|
124
|
+
api.post(topic, asset, objectType, objectId, attribute, options?)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Parameter | Type | Description |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `topic` | `UnsTopics` | UNS topic path prefix (e.g. `"enterprise/site/area/line/"`) |
|
|
130
|
+
| `asset` | `UnsAsset` | Asset identifier (e.g. `"line-3-furnace"`) |
|
|
131
|
+
| `objectType` | `UnsObjectType` | UNS object type (e.g. `"energy-resource"`) |
|
|
132
|
+
| `objectId` | `UnsObjectId` | Object instance id (e.g. `"main-bus"`) |
|
|
133
|
+
| `attribute` | `UnsAttribute` | Attribute name (e.g. `"current"`) |
|
|
134
|
+
| `options` | `IGetEndpointOptions` / `IPostEndpointOptions` | Tags, description, query params / request body |
|
|
135
|
+
|
|
136
|
+
## Auth options
|
|
137
|
+
|
|
138
|
+
| Option | When to use |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `jwks.wellKnownJwksUrl` | Production — verifies RS256 tokens from the UNS controller JWKS endpoint |
|
|
141
|
+
| `jwks.activeKidUrl` | Optional companion to JWKS — narrows which key ID is active |
|
|
142
|
+
| `jwtSecret` | Development / simple deployments — symmetric secret |
|
|
143
|
+
|
|
144
|
+
Requests that fail auth return `401 Unauthorized`. Requests whose token `accessRules` do not match the endpoint path return `403 Forbidden`.
|
|
145
|
+
|
|
146
|
+
## Unregistering an endpoint
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
await api.unregister(topic, asset, objectType, objectId, attribute, "GET");
|
|
150
|
+
await api.unregister(topic, asset, objectType, objectId, attribute, "POST");
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This removes the route from Express, the Swagger spec, and the internal UNS endpoint registry.
|
|
154
|
+
|
|
155
|
+
## registerCatchAll (uns-api-global only)
|
|
62
156
|
|
|
63
|
-
`
|
|
64
|
-
|
|
157
|
+
`api.registerCatchAll()` is reserved for the **uns-api-global** microservice, which acts as a
|
|
158
|
+
catch-all gateway for an entire topic namespace. **Regular microservices must not call this** —
|
|
159
|
+
use `api.get()` / `api.post()` for per-attribute endpoints instead.
|
|
65
160
|
|
|
66
161
|
## Scripts
|
|
67
162
|
|
package/dist/uns-api-proxy.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UnsAttribute } from "@uns-kit/core/uns/uns-interfaces.js";
|
|
2
2
|
import UnsProxy from "@uns-kit/core/uns/uns-proxy.js";
|
|
3
3
|
import { UnsTopics } from "@uns-kit/core/uns/uns-topics.js";
|
|
4
|
-
import { IApiProxyOptions, IGetEndpointOptions } from "@uns-kit/core/uns/uns-interfaces.js";
|
|
4
|
+
import { IApiProxyOptions, IGetEndpointOptions, IPostEndpointOptions } from "@uns-kit/core/uns/uns-interfaces.js";
|
|
5
5
|
import { UnsAsset } from "@uns-kit/core/uns/uns-asset.js";
|
|
6
6
|
import { UnsObjectType, UnsObjectId } from "@uns-kit/core/uns/uns-object.js";
|
|
7
7
|
export default class UnsApiProxy extends UnsProxy {
|
|
@@ -37,6 +37,10 @@ export default class UnsApiProxy extends UnsProxy {
|
|
|
37
37
|
/**
|
|
38
38
|
* Register a catch-all API mapping for a topic prefix (e.g., "sij/acroni/#").
|
|
39
39
|
* Does not create individual API attribute nodes; the controller treats this as a fallback.
|
|
40
|
+
*
|
|
41
|
+
* This is intended for use by the uns-api-global microservice, which acts as a
|
|
42
|
+
* catch-all gateway for an entire topic namespace. Regular microservices should
|
|
43
|
+
* NOT call this — use registerGetEndpoint() for individual attribute endpoints instead.
|
|
40
44
|
*/
|
|
41
45
|
registerCatchAll(topicPrefix: string, options?: {
|
|
42
46
|
apiBase?: string;
|
|
@@ -47,7 +51,15 @@ export default class UnsApiProxy extends UnsProxy {
|
|
|
47
51
|
tags?: string[];
|
|
48
52
|
queryParams?: IGetEndpointOptions["queryParams"];
|
|
49
53
|
}): Promise<void>;
|
|
50
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Register a POST endpoint.
|
|
56
|
+
* @param topic - The API topic
|
|
57
|
+
* @param attribute - The attribute for the topic.
|
|
58
|
+
* @param options.apiDescription - Optional description.
|
|
59
|
+
* @param options.tags - Optional tags.
|
|
60
|
+
* @param options.requestBody - Optional request body schema for Swagger.
|
|
61
|
+
*/
|
|
62
|
+
post(topic: UnsTopics, asset: UnsAsset, objectType: UnsObjectType, objectId: UnsObjectId, attribute: UnsAttribute, options?: IPostEndpointOptions): Promise<void>;
|
|
51
63
|
private emitStatusMetrics;
|
|
52
64
|
private registerHealthEndpoint;
|
|
53
65
|
private extractBearerToken;
|
package/dist/uns-api-proxy.js
CHANGED
|
@@ -296,6 +296,10 @@ export default class UnsApiProxy extends UnsProxy {
|
|
|
296
296
|
/**
|
|
297
297
|
* Register a catch-all API mapping for a topic prefix (e.g., "sij/acroni/#").
|
|
298
298
|
* Does not create individual API attribute nodes; the controller treats this as a fallback.
|
|
299
|
+
*
|
|
300
|
+
* This is intended for use by the uns-api-global microservice, which acts as a
|
|
301
|
+
* catch-all gateway for an entire topic namespace. Regular microservices should
|
|
302
|
+
* NOT call this — use registerGetEndpoint() for individual attribute endpoints instead.
|
|
299
303
|
*/
|
|
300
304
|
async registerCatchAll(topicPrefix, options) {
|
|
301
305
|
while (this.app.server.listening === false) {
|
|
@@ -379,9 +383,141 @@ export default class UnsApiProxy extends UnsProxy {
|
|
|
379
383
|
swaggerPath,
|
|
380
384
|
});
|
|
381
385
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Register a POST endpoint.
|
|
388
|
+
* @param topic - The API topic
|
|
389
|
+
* @param attribute - The attribute for the topic.
|
|
390
|
+
* @param options.apiDescription - Optional description.
|
|
391
|
+
* @param options.tags - Optional tags.
|
|
392
|
+
* @param options.requestBody - Optional request body schema for Swagger.
|
|
393
|
+
*/
|
|
394
|
+
async post(topic, asset, objectType, objectId, attribute, options) {
|
|
395
|
+
while (this.app.server.listening === false) {
|
|
396
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
397
|
+
}
|
|
398
|
+
const time = UnsPacket.formatToISO8601(new Date());
|
|
399
|
+
const fullPath = buildUnsRoutePath(topic, asset, objectType, objectId, attribute);
|
|
400
|
+
const apiPath = `${this.apiBasePrefix}${fullPath}`.replace(/\/{2,}/g, "/");
|
|
401
|
+
const swaggerPath = buildSwaggerPath(this.swaggerBasePrefix, this.processName, this.instanceName);
|
|
402
|
+
try {
|
|
403
|
+
const addressInfo = this.app.server.address();
|
|
404
|
+
let ip;
|
|
405
|
+
let port;
|
|
406
|
+
if (addressInfo && typeof addressInfo === "object") {
|
|
407
|
+
ip = App.getExternalIPv4();
|
|
408
|
+
port = addressInfo.port;
|
|
409
|
+
}
|
|
410
|
+
else if (typeof addressInfo === "string") {
|
|
411
|
+
ip = App.getExternalIPv4();
|
|
412
|
+
port = "";
|
|
413
|
+
}
|
|
414
|
+
this.registerApiEndpoint({
|
|
415
|
+
timestamp: time,
|
|
416
|
+
topic,
|
|
417
|
+
attribute,
|
|
418
|
+
apiHost: `http://${ip}:${port}`,
|
|
419
|
+
apiEndpoint: apiPath,
|
|
420
|
+
apiMethod: "POST",
|
|
421
|
+
apiQueryParams: [],
|
|
422
|
+
apiDescription: options?.apiDescription,
|
|
423
|
+
attributeType: UnsAttributeType.Api,
|
|
424
|
+
apiSwaggerEndpoint: swaggerPath,
|
|
425
|
+
asset,
|
|
426
|
+
objectType,
|
|
427
|
+
objectId,
|
|
428
|
+
});
|
|
429
|
+
const handler = (req, res) => {
|
|
430
|
+
this.event.emit("apiPostEvent", { req, res });
|
|
431
|
+
};
|
|
432
|
+
if (this.options?.jwks?.wellKnownJwksUrl) {
|
|
433
|
+
this.app.router.post(fullPath, async (req, res) => {
|
|
434
|
+
try {
|
|
435
|
+
const token = this.extractBearerToken(req, res);
|
|
436
|
+
if (!token)
|
|
437
|
+
return;
|
|
438
|
+
const publicKey = await this.getPublicKeyFromJwks(token);
|
|
439
|
+
const algorithms = this.options.jwks.algorithms || ["RS256"];
|
|
440
|
+
const decoded = jwt.verify(token, publicKey, { algorithms });
|
|
441
|
+
const accessRules = Array.isArray(decoded?.accessRules)
|
|
442
|
+
? decoded.accessRules
|
|
443
|
+
: (typeof decoded?.pathFilter === "string" && decoded.pathFilter.length > 0
|
|
444
|
+
? [decoded.pathFilter]
|
|
445
|
+
: undefined);
|
|
446
|
+
const allowed = Array.isArray(accessRules)
|
|
447
|
+
? accessRules.some((rule) => UnsTopicMatcher.matches(rule, fullPath))
|
|
448
|
+
: false;
|
|
449
|
+
if (!allowed) {
|
|
450
|
+
return res.status(403).json({ error: "Path not allowed by token access rules" });
|
|
451
|
+
}
|
|
452
|
+
handler(req, res);
|
|
453
|
+
}
|
|
454
|
+
catch (err) {
|
|
455
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else if (this.options?.jwtSecret) {
|
|
460
|
+
this.app.router.post(fullPath, (req, res) => {
|
|
461
|
+
const authHeader = req.headers["authorization"];
|
|
462
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
463
|
+
return res.status(401).json({ error: "Missing or invalid Authorization header" });
|
|
464
|
+
}
|
|
465
|
+
const token = authHeader.slice(7);
|
|
466
|
+
try {
|
|
467
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET || this.options.jwtSecret);
|
|
468
|
+
const accessRules = Array.isArray(decoded?.accessRules)
|
|
469
|
+
? decoded.accessRules
|
|
470
|
+
: (typeof decoded?.pathFilter === "string" && decoded.pathFilter.length > 0
|
|
471
|
+
? [decoded.pathFilter]
|
|
472
|
+
: undefined);
|
|
473
|
+
const allowed = Array.isArray(accessRules)
|
|
474
|
+
? accessRules.some((rule) => UnsTopicMatcher.matches(rule, fullPath))
|
|
475
|
+
: false;
|
|
476
|
+
if (!allowed) {
|
|
477
|
+
return res.status(403).json({ error: "Path not allowed by token access rules" });
|
|
478
|
+
}
|
|
479
|
+
handler(req, res);
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
this.app.router.post(fullPath, handler);
|
|
488
|
+
}
|
|
489
|
+
if (this.app.swaggerSpec) {
|
|
490
|
+
const requestBody = options?.requestBody;
|
|
491
|
+
this.app.swaggerSpec.paths = this.app.swaggerSpec.paths || {};
|
|
492
|
+
this.app.swaggerSpec.paths[apiPath] = this.app.swaggerSpec.paths[apiPath] || {};
|
|
493
|
+
this.app.swaggerSpec.paths[apiPath].post = {
|
|
494
|
+
summary: options?.apiDescription || "No description",
|
|
495
|
+
tags: options?.tags || [],
|
|
496
|
+
...(requestBody
|
|
497
|
+
? {
|
|
498
|
+
requestBody: {
|
|
499
|
+
description: requestBody.description,
|
|
500
|
+
required: requestBody.required ?? true,
|
|
501
|
+
content: {
|
|
502
|
+
"application/json": {
|
|
503
|
+
schema: requestBody.schema ?? { type: "object" },
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
}
|
|
508
|
+
: {}),
|
|
509
|
+
responses: {
|
|
510
|
+
"200": { description: "OK" },
|
|
511
|
+
"400": { description: "Bad Request" },
|
|
512
|
+
"401": { description: "Unauthorized" },
|
|
513
|
+
"403": { description: "Forbidden" },
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
logger.error(`${this.instanceNameWithSuffix} - Error registering POST route ${fullPath}: ${error.message}`);
|
|
520
|
+
}
|
|
385
521
|
}
|
|
386
522
|
emitStatusMetrics() {
|
|
387
523
|
const uptimeMinutes = Math.round((Date.now() - this.startedAt) / 60000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uns-kit/api",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.31",
|
|
4
4
|
"description": "Express-powered API gateway plugin for UnsProxyProcess with JWT/JWKS support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"cookie-parser": "^1.4.7",
|
|
36
36
|
"express": "^5.1.0",
|
|
37
37
|
"multer": "^2.0.2",
|
|
38
|
-
"@uns-kit/core": "2.0.
|
|
38
|
+
"@uns-kit/core": "2.0.31"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/jsonwebtoken": "^9.0.10",
|