@uns-kit/api 0.0.42 → 0.0.45

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/dist/app.d.ts CHANGED
@@ -18,6 +18,7 @@ export default class App {
18
18
  };
19
19
  paths: Record<string, any>;
20
20
  };
21
+ private swaggerDocs;
21
22
  constructor(port: number, processName: string, instanceName: string, appContext?: any);
22
23
  static getExternalIPv4(): string | null;
23
24
  getSwaggerSpec(): {
@@ -28,5 +29,6 @@ export default class App {
28
29
  };
29
30
  paths: Record<string, any>;
30
31
  };
32
+ registerSwaggerDoc(path: string, doc: Record<string, unknown>): void;
31
33
  start(): Promise<void>;
32
34
  }
package/dist/app.js CHANGED
@@ -16,6 +16,7 @@ export default class App {
16
16
  processName;
17
17
  instanceName;
18
18
  swaggerSpec;
19
+ swaggerDocs = new Map();
19
20
  constructor(port, processName, instanceName, appContext) {
20
21
  this.router = express.Router();
21
22
  this.port = port;
@@ -68,6 +69,11 @@ export default class App {
68
69
  getSwaggerSpec() {
69
70
  return this.swaggerSpec;
70
71
  }
72
+ registerSwaggerDoc(path, doc) {
73
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
74
+ this.swaggerDocs.set(normalizedPath, doc);
75
+ this.expressApplication.get(normalizedPath, (_req, res) => res.json(doc));
76
+ }
71
77
  async start() {
72
78
  // Listen on provided port, on all network interfaces.
73
79
  this.server.listen(this.port);
@@ -27,6 +27,12 @@ const unsApiPlugin = ({ define }) => {
27
27
  properties: { messageExpiryInterval: 120000 },
28
28
  });
29
29
  });
30
+ unsApiProxy.event.on("unsProxyProducedApiCatchAll", (event) => {
31
+ internals.processMqttProxy.publish(event.statusTopic, JSON.stringify(event.producedCatchall), {
32
+ retain: true,
33
+ properties: { messageExpiryInterval: 120000 },
34
+ });
35
+ });
30
36
  internals.unsApiProxies.push(unsApiProxy);
31
37
  getApiProxies(this).push(unsApiProxy);
32
38
  return unsApiProxy;
@@ -12,6 +12,7 @@ export default class UnsApiProxy extends UnsProxy {
12
12
  private app;
13
13
  private options;
14
14
  private jwksCache?;
15
+ private catchAllRouteRegistered;
15
16
  constructor(processName: string, instanceName: string, options: IApiProxyOptions);
16
17
  /**
17
18
  * Unregister endpoint
@@ -28,6 +29,19 @@ export default class UnsApiProxy extends UnsProxy {
28
29
  * @param options.tags - Optional tags.
29
30
  */
30
31
  get(topic: UnsTopics, asset: UnsAsset, objectType: UnsObjectType, objectId: UnsObjectId, attribute: UnsAttribute, options?: IGetEndpointOptions): Promise<void>;
32
+ /**
33
+ * Register a catch-all API mapping for a topic prefix (e.g., "sij/acroni/#").
34
+ * Does not create individual API attribute nodes; the controller treats this as a fallback.
35
+ */
36
+ registerCatchAll(topicPrefix: string, options?: {
37
+ apiBase?: string;
38
+ apiBasePath?: string;
39
+ swaggerPath?: string;
40
+ swaggerDoc?: Record<string, unknown>;
41
+ apiDescription?: string;
42
+ tags?: string[];
43
+ queryParams?: IGetEndpointOptions["queryParams"];
44
+ }): Promise<void>;
31
45
  post(..._args: any[]): any;
32
46
  private extractBearerToken;
33
47
  private getPublicKeyFromJwks;
@@ -20,6 +20,7 @@ export default class UnsApiProxy extends UnsProxy {
20
20
  app;
21
21
  options;
22
22
  jwksCache;
23
+ catchAllRouteRegistered = false;
23
24
  constructor(processName, instanceName, options) {
24
25
  super();
25
26
  this.options = options;
@@ -221,6 +222,92 @@ export default class UnsApiProxy extends UnsProxy {
221
222
  logger.error(`${this.instanceNameWithSuffix} - Error publishing message to topic ${topic}${attribute}: ${error.message}`);
222
223
  }
223
224
  }
225
+ /**
226
+ * Register a catch-all API mapping for a topic prefix (e.g., "sij/acroni/#").
227
+ * Does not create individual API attribute nodes; the controller treats this as a fallback.
228
+ */
229
+ async registerCatchAll(topicPrefix, options) {
230
+ while (this.app.server.listening === false) {
231
+ await new Promise((resolve) => setTimeout(resolve, 100));
232
+ }
233
+ const finalOptions = options ?? {};
234
+ const topicNormalized = topicPrefix.endsWith("/") ? topicPrefix : `${topicPrefix}`;
235
+ const addressInfo = this.app.server.address();
236
+ let ip;
237
+ let port;
238
+ if (addressInfo && typeof addressInfo === "object") {
239
+ ip = App.getExternalIPv4();
240
+ port = addressInfo.port;
241
+ }
242
+ else if (typeof addressInfo === "string") {
243
+ ip = App.getExternalIPv4();
244
+ port = "";
245
+ }
246
+ const apiBase = typeof finalOptions?.apiBase === "string" && finalOptions.apiBase.length
247
+ ? finalOptions.apiBase
248
+ : `http://${ip}:${port}`;
249
+ const apiBasePath = typeof finalOptions?.apiBasePath === "string" && finalOptions.apiBasePath.length
250
+ ? finalOptions.apiBasePath
251
+ : "/api";
252
+ const swaggerPath = typeof finalOptions?.swaggerPath === "string" && finalOptions.swaggerPath.length
253
+ ? finalOptions.swaggerPath
254
+ : `/${this.processName}/${this.instanceName}/catchall-swagger.json`;
255
+ const normalizedSwaggerPath = swaggerPath.startsWith("/") ? swaggerPath : `/${swaggerPath}`;
256
+ const swaggerDoc = finalOptions.swaggerDoc ||
257
+ {
258
+ openapi: "3.0.0",
259
+ info: {
260
+ title: "Catch-all API",
261
+ version: "1.0.0",
262
+ },
263
+ paths: {
264
+ "/api/{topicPath}": {
265
+ get: {
266
+ summary: finalOptions.apiDescription || "Catch-all handler",
267
+ tags: finalOptions.tags || [],
268
+ parameters: [
269
+ {
270
+ name: "topicPath",
271
+ in: "path",
272
+ required: true,
273
+ schema: { type: "string" },
274
+ description: "Resolved UNS topic path",
275
+ },
276
+ ...(finalOptions.queryParams || []).map((p) => ({
277
+ name: p.name,
278
+ in: "query",
279
+ required: !!p.required,
280
+ schema: { type: p.type },
281
+ description: p.description,
282
+ })),
283
+ ],
284
+ responses: {
285
+ "200": { description: "OK" },
286
+ "400": { description: "Bad Request" },
287
+ "401": { description: "Unauthorized" },
288
+ "403": { description: "Forbidden" },
289
+ },
290
+ },
291
+ },
292
+ },
293
+ };
294
+ this.app.registerSwaggerDoc(normalizedSwaggerPath, swaggerDoc);
295
+ logger.info(`${this.instanceNameWithSuffix} - Catch-all Swagger available at ${normalizedSwaggerPath} (target ${apiBase.replace(/\/+$/, "")}${normalizedSwaggerPath})`);
296
+ if (!this.catchAllRouteRegistered) {
297
+ this.app.router.use((req, res) => {
298
+ const topicPath = (req.path ?? "").replace(/^\/+/, "");
299
+ req.params = { ...(req.params || {}), topicPath };
300
+ this.event.emit("apiGetEvent", { req, res });
301
+ });
302
+ this.catchAllRouteRegistered = true;
303
+ }
304
+ this.registerApiCatchAll({
305
+ topic: topicNormalized,
306
+ apiBase,
307
+ apiBasePath,
308
+ swaggerPath,
309
+ });
310
+ }
224
311
  post(..._args) {
225
312
  // Implement POST logic or route binding here
226
313
  return "POST called";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uns-kit/api",
3
- "version": "0.0.42",
3
+ "version": "0.0.45",
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": "1.0.32"
38
+ "@uns-kit/core": "1.0.36"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/jsonwebtoken": "^9.0.10",