@heswell/vuu-portal 2.2.0 → 2.2.2

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/195.js CHANGED
@@ -1,5 +1,5 @@
1
- import { ConfigFactory, EditSessionRpcHandler, ModuleFactory, Provider, TableDef, VUU_DEFAULT_COLUMNS, ViewPortDef, createConfiguredAuthProviders, createVuuServerApplication, getLogger } from "@heswell/vuu-server";
2
- import { MODULE_ADMIN_RPC, executeModuleAdminRpc, managedModuleColumnValues, managedModulePermissionValues, validateModuleConfig } from "@heswell/module-admin";
1
+ import { MODULE_ADMIN_RPC, executeModuleAdminRpc, managedModuleColumnValues, managedModulePermissionValues, splitNotificationTopics, validateModuleConfig } from "@heswell/module-admin";
2
+ import { ConfigFactory, EditSessionRpcHandler, ModuleFactory, NotificationModule, NullProvider, PermissionFilter, Provider, TableDef, VUU_DEFAULT_COLUMNS, ViewPortDef, createConfiguredAuthProviders, createVuuServerApplication, getLogger } from "@heswell/vuu-server";
3
3
  import node_fs from "node:fs";
4
4
  import node_path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -31,7 +31,8 @@ function readModules(table) {
31
31
  mfComponent: stringValue(table, row, "mfComponent"),
32
32
  mfScope: stringValue(table, row, "mfScope"),
33
33
  mfUrl: stringValue(table, row, "mfUrl"),
34
- ...navIcon(table, row)
34
+ ...navIcon(table, row),
35
+ ...navMetadata(table, row)
35
36
  };
36
37
  });
37
38
  }
@@ -41,6 +42,23 @@ function navIcon(table, row) {
41
42
  navIconUrl
42
43
  } : {};
43
44
  }
45
+ function navMetadata(table, row) {
46
+ const metadata = {};
47
+ const navOrder = optionalValue(table, row, "navOrder");
48
+ if ("number" == typeof navOrder && Number.isFinite(navOrder)) metadata.navOrder = navOrder;
49
+ const navBadge = optionalValue(table, row, "navBadge");
50
+ if ("string" == typeof navBadge && navBadge) metadata.navBadge = navBadge;
51
+ const topics = optionalValue(table, row, "notificationTopics");
52
+ if ("string" == typeof topics) {
53
+ const notificationTopics = splitNotificationTopics(topics);
54
+ if (notificationTopics.length) metadata.notificationTopics = notificationTopics;
55
+ }
56
+ return metadata;
57
+ }
58
+ function optionalValue(table, row, column) {
59
+ const index = table.tableDef.columns.findIndex(({ name })=>name === column);
60
+ return -1 === index ? void 0 : row[index];
61
+ }
44
62
  function readModulePermissions(table) {
45
63
  return table.rows.map((row)=>({
46
64
  moduleId: numberValue(table, row, "module_id"),
@@ -70,7 +88,13 @@ function selectModules(modules, modulePermissions, authorizations) {
70
88
  ...latestByName.values()
71
89
  ];
72
90
  const permittedIds = new Set(permittedModules.map(({ id })=>id));
73
- return permittedModules.filter(({ parentModuleId })=>0 === parentModuleId || permittedIds.has(parentModuleId)).map(({ parentModuleId: _parentModuleId, ...module })=>module).sort((left, right)=>left.name.localeCompare(right.name));
91
+ return permittedModules.filter(({ parentModuleId })=>0 === parentModuleId || permittedIds.has(parentModuleId)).map(({ parentModuleId: _parentModuleId, ...module })=>module).sort(compareModules);
92
+ }
93
+ function compareModules(left, right) {
94
+ const leftOrder = left.navOrder ?? 1 / 0;
95
+ const rightOrder = right.navOrder ?? 1 / 0;
96
+ if (leftOrder !== rightOrder) return leftOrder < rightOrder ? -1 : 1;
97
+ return left.name.localeCompare(right.name);
74
98
  }
75
99
  function accessRoleForModule(module, moduleRoles, permittedModuleRoles) {
76
100
  const ownRole = permittedModuleRoles.get(module.id);
@@ -152,6 +176,18 @@ const modulesTable = TableDef({
152
176
  name: "navIconUrl",
153
177
  dataType: "string"
154
178
  },
179
+ {
180
+ name: "navOrder",
181
+ dataType: "double"
182
+ },
183
+ {
184
+ name: "navBadge",
185
+ dataType: "string"
186
+ },
187
+ {
188
+ name: "notificationTopics",
189
+ dataType: "string"
190
+ },
155
191
  ...VUU_DEFAULT_COLUMNS
156
192
  ],
157
193
  keyField: "id",
@@ -332,9 +368,12 @@ function readModulesFile(filePath) {
332
368
  return parsed.modules;
333
369
  }
334
370
  function cloneModules(modules) {
335
- return modules.map((module)=>({
336
- ...module
337
- }));
371
+ return modules.map((module)=>Object.fromEntries(Object.entries(module).filter(([, value])=>void 0 !== value).map(([key, value])=>[
372
+ key,
373
+ Array.isArray(value) ? [
374
+ ...value
375
+ ] : value
376
+ ])));
338
377
  }
339
378
  const managedModuleKeys = [
340
379
  "id",
@@ -354,6 +393,11 @@ const managedModuleKeys = [
354
393
  "navIconUrl",
355
394
  "accessRole"
356
395
  ];
396
+ const optionalModuleKeys = [
397
+ "navOrder",
398
+ "navBadge",
399
+ "notificationTopics"
400
+ ];
357
401
  const retiredModuleKeys = [
358
402
  "vuuConnectionId",
359
403
  "vuuWebsocketUrl",
@@ -361,7 +405,7 @@ const retiredModuleKeys = [
361
405
  ];
362
406
  function parseManagedModule(value, index) {
363
407
  if (!isRecord(value)) throw new Error(`Modules file entry ${index} must be an object`);
364
- for (const key of Object.keys(value))if (!managedModuleKeys.includes(key) && !retiredModuleKeys.includes(key)) throw new Error(`Modules file entry ${index} contains unknown field '${key}'`);
408
+ for (const key of Object.keys(value))if (!managedModuleKeys.includes(key) && !optionalModuleKeys.includes(key) && !retiredModuleKeys.includes(key)) throw new Error(`Modules file entry ${index} contains unknown field '${key}'`);
365
409
  const module = {};
366
410
  for (const key of managedModuleKeys){
367
411
  const fieldValue = value[key];
@@ -377,6 +421,18 @@ function parseManagedModule(value, index) {
377
421
  if ("number" === expected && !Number.isInteger(fieldValue)) throw new Error(`Modules file entry ${index} field '${key}' must be an integer`);
378
422
  module[key] = fieldValue;
379
423
  }
424
+ for (const key of optionalModuleKeys){
425
+ const fieldValue = value[key];
426
+ if (null == fieldValue) continue;
427
+ const valid = "navOrder" === key ? "number" == typeof fieldValue : "navBadge" === key ? "string" == typeof fieldValue : Array.isArray(fieldValue) && fieldValue.every((topic)=>"string" == typeof topic);
428
+ if (!valid) {
429
+ const expected = "navOrder" === key ? "a number" : "navBadge" === key ? "a string" : "a list of strings";
430
+ throw new Error(`Modules file entry ${index} field '${key}' must be ${expected}`);
431
+ }
432
+ module[key] = Array.isArray(fieldValue) ? [
433
+ ...fieldValue
434
+ ] : fieldValue;
435
+ }
380
436
  return module;
381
437
  }
382
438
  function assertValidManagedModules(modules, filePath) {
@@ -498,6 +554,10 @@ function createPortalApplication({ config, configFile } = {}) {
498
554
  moduleRegistry: createModuleRegistry(tableContainer, user)
499
555
  }),
500
556
  modules: [
557
+ NotificationModule((table)=>new NullProvider(table), (viewport)=>PermissionFilter("audience", new Set([
558
+ "all",
559
+ viewport.user.name
560
+ ])), (table)=>ViewPortDef["default"](table.tableDef.columns)),
501
561
  ModuleDiscoveryModule(loadModuleState(portalConfig), {
502
562
  adminRole: loadModuleAdminRole(portalConfig)
503
563
  })
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @heswell/vuu-portal
2
2
 
3
3
  Vuu portal server: a [`@heswell/vuu-server`](../vuu-server) application that
4
- hosts the `MODULE_DISCOVERY` module and returns each user's module registry in
4
+ hosts the `MODULE_DISCOVERY` and `NOTIFICATIONS` modules and returns each user's module registry in
5
5
  the login response. Requires the [Bun](https://bun.sh) runtime to run the
6
6
  server. The library exports can be imported under Node.js too.
7
7
 
@@ -19,4 +19,8 @@
19
19
  # mfUrl: "http://localhost:5002"
20
20
  # navIconUrl: ""
21
21
  # accessRole: "module-admin-access"
22
+ # # Optional nav metadata:
23
+ # navOrder: 1
24
+ # navBadge: "modules.pending"
25
+ # notificationTopics: ["modules"]
22
26
  modules: []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heswell/vuu-portal",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "VUU portal server with remote module discovery",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -26,7 +26,7 @@
26
26
  "author": "heswell",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "@heswell/module-admin": "^2.0.1"
29
+ "@heswell/module-admin": "^2.1.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@heswell/vuu-server": "^3.1.0 || ^4.0.0",
@@ -6,48 +6,14 @@ export interface ModuleStore {
6
6
  export declare class InMemoryModuleStore implements ModuleStore {
7
7
  private modules;
8
8
  constructor(modules?: readonly ManagedModule[]);
9
- load(): {
10
- parentModuleId: number;
11
- name: string;
12
- title: string;
13
- description: string;
14
- enabled: boolean;
15
- location: string;
16
- path: string;
17
- mfComponent: string;
18
- mfScope: string;
19
- mfUrl: string;
20
- navIconUrl: string;
21
- accessRole: string;
22
- id: number;
23
- version: number;
24
- created: number;
25
- updated: number;
26
- }[];
9
+ load(): ManagedModule[];
27
10
  save(modules: readonly ManagedModule[]): void;
28
11
  }
29
12
  export declare class ModuleState {
30
13
  #private;
31
14
  private readonly store;
32
15
  constructor(store: ModuleStore, modules: readonly ManagedModule[]);
33
- get modules(): {
34
- parentModuleId: number;
35
- name: string;
36
- title: string;
37
- description: string;
38
- enabled: boolean;
39
- location: string;
40
- path: string;
41
- mfComponent: string;
42
- mfScope: string;
43
- mfUrl: string;
44
- navIconUrl: string;
45
- accessRole: string;
46
- id: number;
47
- version: number;
48
- created: number;
49
- updated: number;
50
- }[];
16
+ get modules(): ManagedModule[];
51
17
  replaceAndSave(modules: readonly ManagedModule[]): void;
52
18
  }
53
19
  export declare class YamlModuleStore implements ModuleStore {