@vuu-ui/vuu-data-test 4.0.0-alpha.17 → 4.0.0-alpha.19

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 (32) hide show
  1. package/package.json +8 -8
  2. package/src/core/filter/PermissionFilter.js +60 -0
  3. package/src/core/filter/PermissionFilteredTable.js +59 -0
  4. package/src/core/module/VuuModule.js +27 -3
  5. package/src/core/user/CurrentUser.js +13 -0
  6. package/src/index.js +4 -0
  7. package/src/local-datasource-provider/LocalDatasourceProvider.js +2 -1
  8. package/src/module-admin/ModuleAdminModule.js +84 -20
  9. package/src/module-admin/module-admin-schemas.js +8 -0
  10. package/src/module-admin/snapshot-projection.js +3 -31
  11. package/src/notifications/NotificationModule.js +93 -0
  12. package/src/notifications/NotificationsSchema.js +52 -0
  13. package/src/notifications/index.js +6 -0
  14. package/src/notifications/simulated/DismissNotificationRpcHandler.js +36 -0
  15. package/src/notifications/simulated/SimulatedNotificationsModule.js +14 -0
  16. package/src/notifications/simulated/SimulatedNotificationsProvider.js +151 -0
  17. package/src/notifications/simulated/notificationsModule.js +3 -0
  18. package/types/core/filter/PermissionFilter.d.ts +41 -0
  19. package/types/core/filter/PermissionFilteredTable.d.ts +24 -0
  20. package/types/core/module/VuuModule.d.ts +19 -0
  21. package/types/core/user/CurrentUser.d.ts +14 -0
  22. package/types/index.d.ts +4 -0
  23. package/types/module-admin/ModuleAdminModule.d.ts +12 -3
  24. package/types/module-admin/index.d.ts +1 -1
  25. package/types/module-admin/snapshot-projection.d.ts +3 -4
  26. package/types/notifications/NotificationModule.d.ts +39 -0
  27. package/types/notifications/NotificationsSchema.d.ts +20 -0
  28. package/types/notifications/index.d.ts +6 -0
  29. package/types/notifications/simulated/DismissNotificationRpcHandler.d.ts +8 -0
  30. package/types/notifications/simulated/SimulatedNotificationsModule.d.ts +2 -0
  31. package/types/notifications/simulated/SimulatedNotificationsProvider.d.ts +21 -0
  32. package/types/notifications/simulated/notificationsModule.d.ts +5 -0
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0-alpha.17",
2
+ "version": "4.0.0-alpha.19",
3
3
  "main": "./src/index.js",
4
4
  "type": "module",
5
5
  "author": "heswell",
@@ -10,16 +10,16 @@
10
10
  "type-defs": "node ../../scripts/build-type-defs.mjs"
11
11
  },
12
12
  "dependencies": {
13
- "@heswell/module-admin": "1.0.0",
13
+ "@heswell/module-admin": "1.1.0",
14
14
  "@heswell/user-admin": "1.0.1",
15
- "@vuu-ui/vuu-data-editing": "4.0.0-alpha.17",
16
- "@vuu-ui/vuu-data-local": "4.0.0-alpha.17",
17
- "@vuu-ui/vuu-utils": "4.0.0-alpha.17"
15
+ "@vuu-ui/vuu-data-editing": "4.0.0-alpha.19",
16
+ "@vuu-ui/vuu-data-local": "4.0.0-alpha.19",
17
+ "@vuu-ui/vuu-utils": "4.0.0-alpha.19"
18
18
  },
19
19
  "devDependencies": {
20
- "@vuu-ui/vuu-data-types": "4.0.0-alpha.17",
21
- "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.17",
22
- "@vuu-ui/vuu-table-types": "4.0.0-alpha.17"
20
+ "@vuu-ui/vuu-data-types": "4.0.0-alpha.19",
21
+ "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.19",
22
+ "@vuu-ui/vuu-table-types": "4.0.0-alpha.19"
23
23
  },
24
24
  "sideEffects": false,
25
25
  "files": [
@@ -0,0 +1,60 @@
1
+ const allowAll = ()=>true;
2
+ const denyAll = ()=>false;
3
+ const AllowAllPermissionFilter = {
4
+ createPredicate: ()=>allowAll
5
+ };
6
+ const DenyAllPermissionFilter = {
7
+ createPredicate: ()=>denyAll
8
+ };
9
+ class RowPermissionFilter {
10
+ rowPredicate;
11
+ constructor(rowPredicate){
12
+ this.rowPredicate = rowPredicate;
13
+ }
14
+ createPredicate(columnMap) {
15
+ let currentRow = [];
16
+ const rowData = {
17
+ get: (columnName)=>{
18
+ const idx = columnMap[columnName];
19
+ return void 0 === idx ? void 0 : currentRow[idx];
20
+ }
21
+ };
22
+ return (row)=>{
23
+ currentRow = row;
24
+ return this.rowPredicate(rowData);
25
+ };
26
+ }
27
+ }
28
+ class ContainsPermissionFilter {
29
+ columnName;
30
+ allowedValues;
31
+ constructor(columnName, allowedValues){
32
+ this.columnName = columnName;
33
+ this.allowedValues = allowedValues;
34
+ }
35
+ createPredicate(columnMap) {
36
+ const idx = columnMap[this.columnName];
37
+ if (void 0 === idx || 0 === this.allowedValues.size) return denyAll;
38
+ const { allowedValues } = this;
39
+ return (row)=>{
40
+ const value = row[idx];
41
+ return null != value && allowedValues.has(String(value));
42
+ };
43
+ }
44
+ }
45
+ class PermissionFilterChain {
46
+ filters;
47
+ constructor(filters){
48
+ this.filters = filters;
49
+ }
50
+ createPredicate(columnMap) {
51
+ const predicates = this.filters.map((f)=>f.createPredicate(columnMap));
52
+ return (row)=>predicates.every((predicate)=>predicate(row));
53
+ }
54
+ }
55
+ function PermissionFilter(arg, allowedValues) {
56
+ if ("function" == typeof arg) return new RowPermissionFilter(arg);
57
+ if ("string" == typeof arg) return new ContainsPermissionFilter(arg, allowedValues ?? new Set());
58
+ return new PermissionFilterChain(Array.from(arg));
59
+ }
60
+ export { AllowAllPermissionFilter, DenyAllPermissionFilter, PermissionFilter };
@@ -0,0 +1,59 @@
1
+ import { Table } from "../../Table.js";
2
+ class PermissionFilteredTable extends Table {
3
+ #attached = false;
4
+ #indexOfKey;
5
+ #predicate;
6
+ #source;
7
+ constructor(source, predicate){
8
+ super(source.schema, source.data.filter(predicate), source.map);
9
+ this.#indexOfKey = source.map[source.schema.key];
10
+ this.#predicate = predicate;
11
+ this.#source = source;
12
+ this.attach();
13
+ }
14
+ get source() {
15
+ return this.#source;
16
+ }
17
+ attach() {
18
+ if (!this.#attached) {
19
+ this.#source.on("insert", this.handleSourceInsert);
20
+ this.#source.on("update", this.handleSourceUpdate);
21
+ this.#source.on("delete", this.handleSourceDelete);
22
+ this.#attached = true;
23
+ }
24
+ }
25
+ dispose() {
26
+ if (this.#attached) {
27
+ this.#source.removeListener("insert", this.handleSourceInsert);
28
+ this.#source.removeListener("update", this.handleSourceUpdate);
29
+ this.#source.removeListener("delete", this.handleSourceDelete);
30
+ this.#attached = false;
31
+ }
32
+ }
33
+ contains(key) {
34
+ return void 0 !== this.findByKey(key);
35
+ }
36
+ handleSourceInsert = (row)=>{
37
+ if (this.#predicate(row)) this.insert(row);
38
+ };
39
+ handleSourceUpdate = (row)=>{
40
+ const key = row[this.#indexOfKey];
41
+ const isMember = this.contains(key);
42
+ const isPermitted = this.#predicate(row);
43
+ if (isMember && isPermitted) this.replaceRow(row);
44
+ else if (isMember) this.delete(key);
45
+ else if (isPermitted) this.insert(row);
46
+ };
47
+ handleSourceDelete = (key)=>{
48
+ if (this.contains(key)) this.delete(key);
49
+ };
50
+ replaceRow(row) {
51
+ const key = row[this.#indexOfKey];
52
+ const rowIndex = this.data.findIndex((r)=>r[this.#indexOfKey] === key);
53
+ if (-1 !== rowIndex) {
54
+ this.data[rowIndex] = row;
55
+ this.emit("update", row);
56
+ }
57
+ }
58
+ }
59
+ export { PermissionFilteredTable };
@@ -4,6 +4,10 @@ import { TickingArrayDataSource } from "../../TickingArrayDataSource.js";
4
4
  import { RuntimeVisualLink } from "../../RuntimeVisualLink.js";
5
5
  import ModuleContainer from "./ModuleContainer.js";
6
6
  import { sessionTableRow, sessionTableSchema } from "../../session-table-utils.js";
7
+ import { AllowAllPermissionFilter } from "../filter/PermissionFilter.js";
8
+ import { PermissionFilteredTable } from "../filter/PermissionFilteredTable.js";
9
+ import { getCurrentUser } from "../user/CurrentUser.js";
10
+ import TableContainer from "../table/TableContainer.js";
7
11
  const assertUpdateIsValid = (schema, column, _data)=>{
8
12
  const col = schema.columns.find((col)=>col.name === column);
9
13
  if (col) ;
@@ -27,6 +31,7 @@ class VuuModule {
27
31
  }
28
32
  async beforeSessionSave(_sourceTable, _sessionTable) {}
29
33
  async afterSessionSave(_sourceTable, _sessionTable) {}
34
+ permissionFunctions;
30
35
  getTableSchema(tableName) {
31
36
  return this.schemas[tableName] ?? this.#sessionTableMap[tableName]?.schema;
32
37
  }
@@ -126,9 +131,17 @@ class VuuModule {
126
131
  return void 0 === linksForTable ? void 0 : this.getLinks(this.#subscriptionMap, linksForTable);
127
132
  };
128
133
  createDataSource = (tableName, viewport, config)=>{
134
+ this.beforeCreateDataSource?.(tableName);
129
135
  const columnDescriptors = this.getColumnDescriptors(tableName);
130
- const table = this.tables[tableName];
131
136
  const sessionTable = this.#sessionTableMap[tableName];
137
+ const user = getCurrentUser();
138
+ const permissionFunction = sessionTable ? void 0 : this.permissionFunctions?.[tableName];
139
+ const viewportId = permissionFunction ? viewport ?? uuid() : viewport;
140
+ const permissionFilteredTable = permissionFunction ? this.createPermissionFilteredTable(this.tables[tableName], permissionFunction, {
141
+ id: viewportId,
142
+ user
143
+ }) : void 0;
144
+ const table = permissionFilteredTable ?? this.tables[tableName];
132
145
  const vuuModule = this;
133
146
  const dataSource = new TickingArrayDataSource({
134
147
  ...config,
@@ -139,18 +152,24 @@ class VuuModule {
139
152
  menu: this.menus?.[tableName],
140
153
  rpcServices: this.getServices(tableName),
141
154
  rpcMenuServices: this.getMenuServices(tableName),
142
- viewport,
155
+ viewport: viewportId,
143
156
  visualLinkService: this.visualLinkService,
144
157
  vuuModule
145
158
  });
146
159
  dataSource.on("subscribed", this.handlePostSubscribeActivities);
147
160
  dataSource.on("unsubscribed", this.unregisterViewport);
161
+ if (permissionFilteredTable) {
162
+ dataSource.on("subscribed", ()=>permissionFilteredTable.attach());
163
+ dataSource.on("unsubscribed", ()=>permissionFilteredTable.dispose());
164
+ }
148
165
  const existingSubscriptions = this.#subscriptionMap.get(tableName);
149
166
  const subscription = {
150
167
  viewportId: dataSource.viewport,
151
168
  dataSource,
152
169
  sessionTableName: sessionTable ? tableName : void 0,
153
- sourceTableName: this.#sessionSourceTableMap[tableName]
170
+ sourceTableName: this.#sessionSourceTableMap[tableName],
171
+ table: table || sessionTable,
172
+ user
154
173
  };
155
174
  if (existingSubscriptions) existingSubscriptions.push(subscription);
156
175
  else this.#subscriptionMap.set(tableName, [
@@ -158,6 +177,11 @@ class VuuModule {
158
177
  ]);
159
178
  return dataSource;
160
179
  };
180
+ createPermissionFilteredTable(table, permissionFunction, viewport) {
181
+ const permissionFilter = permissionFunction(viewport, TableContainer);
182
+ if (permissionFilter === AllowAllPermissionFilter) return;
183
+ return new PermissionFilteredTable(table, permissionFilter.createPredicate(table.map));
184
+ }
161
185
  getServices(tableName) {
162
186
  const tableServices = this.services?.[tableName];
163
187
  const defaultServices = this.includeDefaultServices ? this.#moduleServices : [];
@@ -0,0 +1,13 @@
1
+ const DEFAULT_USER = {
2
+ name: "user",
3
+ authorizations: []
4
+ };
5
+ let currentUser = DEFAULT_USER;
6
+ const getCurrentUser = ()=>currentUser;
7
+ const setCurrentUser = (user)=>{
8
+ currentUser = void 0 === user ? DEFAULT_USER : "string" == typeof user ? {
9
+ name: user,
10
+ authorizations: []
11
+ } : user;
12
+ };
13
+ export { getCurrentUser, setCurrentUser };
package/src/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  export * from "./ArrayProxy.js";
2
+ export * from "./core/filter/PermissionFilter.js";
3
+ export * from "./core/filter/PermissionFilteredTable.js";
2
4
  export * from "./core/module/VuuModule.js";
5
+ export * from "./core/user/CurrentUser.js";
3
6
  export * from "./local-datasource-provider/LocalDatasourceProvider.js";
4
7
  export * from "./makeSuggestions.js";
8
+ export * from "./notifications/index.js";
5
9
  export * from "./schemas.js";
6
10
  export * from "./simul/index.js";
7
11
  export * from "./basket/index.js";
@@ -23,9 +23,10 @@ const createLocalDataSourceProvider = (moduleNames)=>{
23
23
  };
24
24
  const getServerAPI = async ()=>serverAPI;
25
25
  class VuuDataSource {
26
- constructor({ aggregations, columns, filterSpec, groupBy, session, sort, table, viewport, visualLink }){
26
+ constructor({ aggregations, baseFilterSpec, columns, filterSpec, groupBy, session, sort, table, viewport, visualLink }){
27
27
  const config = {
28
28
  aggregations,
29
+ baseFilterSpec,
29
30
  columns,
30
31
  filterSpec,
31
32
  groupBy,
@@ -1,4 +1,4 @@
1
- import { DEFAULT_MODULE_DEFINITIONS } from "@heswell/module-admin/contracts";
1
+ import { DEFAULT_MODULE_DEFINITIONS, MODULE_ADMIN_RPC, MODULE_NAV_ICONS, executeModuleAdminRpc, toManagedModules } from "@heswell/module-admin/contracts";
2
2
  import { VuuModule } from "../core/module/VuuModule.js";
3
3
  import TableContainer from "../core/table/TableContainer.js";
4
4
  import { buildDataColumnMapFromSchema } from "../Table.js";
@@ -12,31 +12,67 @@ const createTables = ()=>({
12
12
  modulePermissions: createTable("modulePermissions"),
13
13
  modules: createTable("modules")
14
14
  });
15
+ const INITIAL_TIMESTAMP = 1710000000000;
16
+ const INITIAL_ACCESS_ROLES = [
17
+ {
18
+ moduleName: "moduleAdmin",
19
+ role: "module-admin-access"
20
+ },
21
+ {
22
+ moduleName: "userAdmin",
23
+ role: "user-admin-access"
24
+ },
25
+ {
26
+ moduleName: "basket-trading",
27
+ role: "basket-trading-access"
28
+ },
29
+ {
30
+ moduleName: "vuu-table-browser",
31
+ role: "vuu-table-browser-access"
32
+ }
33
+ ];
34
+ const EXAMPLE_MODULES = [
35
+ {
36
+ id: 6,
37
+ parentModuleId: 0,
38
+ name: "order-blotter",
39
+ title: "Order blotter",
40
+ description: "Monitor parent and child orders across all desks.",
41
+ version: 3,
42
+ enabled: false,
43
+ location: "/Trading/Orders",
44
+ path: "/trading/orders",
45
+ mfComponent: "OrderBlotter",
46
+ mfScope: "orderBlotter",
47
+ mfUrl: "http://localhost:5009",
48
+ navIconUrl: MODULE_NAV_ICONS.tables,
49
+ vuuConnectionId: "orders",
50
+ vuuWebsocketUrl: "wss://localhost:8095/websocket-orders",
51
+ vuuRestUrl: "https://localhost:8447/api/authn",
52
+ accessRole: "",
53
+ created: INITIAL_TIMESTAMP,
54
+ updated: INITIAL_TIMESTAMP
55
+ }
56
+ ];
15
57
  const MODULE_ADMIN_INITIAL_SNAPSHOT = {
16
- moduleAccessRoles: [
17
- {
18
- moduleName: "moduleAdmin",
19
- role: "module-admin-access"
20
- },
21
- {
22
- moduleName: "userAdmin",
23
- role: "user-admin-access"
24
- },
25
- {
26
- moduleName: "basket-trading",
27
- role: "basket-trading-access"
28
- }
29
- ],
30
- modules: DEFAULT_MODULE_DEFINITIONS,
31
- timestamp: 1710000000000
58
+ modules: [
59
+ ...toManagedModules(DEFAULT_MODULE_DEFINITIONS, INITIAL_ACCESS_ROLES, INITIAL_TIMESTAMP),
60
+ ...EXAMPLE_MODULES
61
+ ]
32
62
  };
33
63
  class ModuleAdminModule extends VuuModule {
64
+ now;
65
+ #modules;
34
66
  #tables;
35
- constructor(snapshot = MODULE_ADMIN_INITIAL_SNAPSHOT){
36
- super(MODULE_ADMIN_MODULE_NAME);
67
+ constructor(snapshot = MODULE_ADMIN_INITIAL_SNAPSHOT, now = Date.now){
68
+ super(MODULE_ADMIN_MODULE_NAME), this.now = now;
69
+ this.#modules = snapshot.modules;
37
70
  this.#tables = createTables();
38
71
  reconcileModuleAdminTables(snapshot, this.#tables);
39
72
  }
73
+ get modules() {
74
+ return this.#modules;
75
+ }
40
76
  get menus() {
41
77
  return {
42
78
  modulePermissions: void 0,
@@ -50,7 +86,35 @@ class ModuleAdminModule extends VuuModule {
50
86
  get schemas() {
51
87
  return MODULE_ADMIN_TABLE_SCHEMAS;
52
88
  }
53
- get services() {}
89
+ service = (rpcName)=>async (request)=>{
90
+ try {
91
+ if ("RPC_REQUEST" !== request.type || request.rpcName !== rpcName) throw new Error(`Expected ${rpcName} RPC request`);
92
+ const { modules, result } = executeModuleAdminRpc(this.#modules, rpcName, request.params, this.now);
93
+ this.#modules = modules;
94
+ reconcileModuleAdminTables({
95
+ modules
96
+ }, this.#tables);
97
+ return {
98
+ data: result,
99
+ type: "SUCCESS_RESULT"
100
+ };
101
+ } catch (error) {
102
+ return {
103
+ errorMessage: error instanceof Error ? error.message : "Module admin request failed",
104
+ type: "ERROR_RESULT"
105
+ };
106
+ }
107
+ };
108
+ get services() {
109
+ const services = Object.keys(MODULE_ADMIN_RPC).map((rpcName)=>({
110
+ rpcName,
111
+ service: this.service(rpcName)
112
+ }));
113
+ return {
114
+ modulePermissions: services,
115
+ modules: services
116
+ };
117
+ }
54
118
  get tables() {
55
119
  return this.#tables;
56
120
  }
@@ -11,6 +11,10 @@ const MODULE_ADMIN_TABLE_SCHEMAS = {
11
11
  name: "id",
12
12
  serverDataType: "int"
13
13
  },
14
+ {
15
+ name: "parentModuleId",
16
+ serverDataType: "int"
17
+ },
14
18
  {
15
19
  name: "name",
16
20
  serverDataType: "string"
@@ -63,6 +67,10 @@ const MODULE_ADMIN_TABLE_SCHEMAS = {
63
67
  name: "vuuRestUrl",
64
68
  serverDataType: "string"
65
69
  },
70
+ {
71
+ name: "navIconUrl",
72
+ serverDataType: "string"
73
+ },
66
74
  ...VUU_DEFAULT_COLUMNS
67
75
  ],
68
76
  key: "id",
@@ -1,37 +1,9 @@
1
- import { moduleDefinitionsToRows, modulePermissionsFor } from "@heswell/module-admin/contracts";
1
+ import { managedModuleColumnValues, managedModulePermissionValues } from "@heswell/module-admin/contracts";
2
2
  const rowsEqual = (left, right)=>left.length === right.length && left.every((value, index)=>value === right[index]);
3
- const systemValues = (timestamp)=>({
4
- vuuCreatedTimestamp: timestamp,
5
- vuuMsg: "",
6
- vuuUpdatedTimestamp: timestamp
7
- });
8
3
  const toRow = (table, values)=>table.schema.columns.map(({ name })=>values[name] ?? "");
9
- const moduleValues = ([id, name, title, description, version, enabled, location, path, mfComponent, mfScope, mfUrl, vuuConnectionId, vuuWebsocketUrl, vuuRestUrl], timestamp)=>({
10
- ...systemValues(timestamp),
11
- description,
12
- enabled,
13
- id,
14
- location,
15
- mfComponent,
16
- mfScope,
17
- mfUrl,
18
- name,
19
- path,
20
- title,
21
- version,
22
- vuuConnectionId,
23
- vuuRestUrl,
24
- vuuWebsocketUrl
25
- });
26
- const permissionValues = ([id, moduleId, role], timestamp)=>({
27
- ...systemValues(timestamp),
28
- id,
29
- module_id: moduleId,
30
- role
31
- });
32
4
  const projectModuleAdminSnapshot = (snapshot, tables)=>({
33
- modulePermissions: modulePermissionsFor(snapshot.modules, snapshot.moduleAccessRoles).map((row)=>toRow(tables.modulePermissions, permissionValues(row, snapshot.timestamp))),
34
- modules: moduleDefinitionsToRows(snapshot.modules).map((row)=>toRow(tables.modules, moduleValues(row, snapshot.timestamp)))
5
+ modulePermissions: managedModulePermissionValues(snapshot.modules).map((values)=>toRow(tables.modulePermissions, values)),
6
+ modules: snapshot.modules.map((module)=>toRow(tables.modules, managedModuleColumnValues(module)))
35
7
  });
36
8
  const reconcileModuleAdminTables = (snapshot, tables)=>{
37
9
  const projected = projectModuleAdminSnapshot(snapshot, tables);
@@ -0,0 +1,93 @@
1
+ import { VuuModule } from "../core/module/VuuModule.js";
2
+ import TableContainer from "../core/table/TableContainer.js";
3
+ import { buildDataColumnMapFromSchema } from "../Table.js";
4
+ import { NotificationsSchema } from "./NotificationsSchema.js";
5
+ const NAME = "NOTIFICATIONS";
6
+ const TABLE_NAME = "notifications";
7
+ const DEFAULT_RANGE_LIMITS = {
8
+ maxRangeEnd: 1000000,
9
+ maxRangeWidth: 1000
10
+ };
11
+ const VUU_DEFAULT_COLUMNS = [
12
+ {
13
+ name: "vuuCreatedTimestamp",
14
+ serverDataType: "epochtimestamp"
15
+ },
16
+ {
17
+ name: "vuuUpdatedTimestamp",
18
+ serverDataType: "epochtimestamp"
19
+ },
20
+ {
21
+ name: "vuuMsg",
22
+ serverDataType: "string"
23
+ }
24
+ ];
25
+ class NotificationModule extends VuuModule {
26
+ static NAME = NAME;
27
+ static TABLE_NAME = TABLE_NAME;
28
+ #provider;
29
+ #running = false;
30
+ #schemas;
31
+ #services;
32
+ #tables;
33
+ constructor(providerFactory, permissionFunction, serviceFactory, ...additionalColumns){
34
+ super(NAME);
35
+ const schema = {
36
+ columns: [
37
+ ...NotificationsSchema.allFrom(...additionalColumns),
38
+ ...VUU_DEFAULT_COLUMNS
39
+ ],
40
+ key: "id",
41
+ rangeLimits: DEFAULT_RANGE_LIMITS,
42
+ table: {
43
+ module: NAME,
44
+ table: TABLE_NAME
45
+ }
46
+ };
47
+ const table = TableContainer.createTable(schema, [], buildDataColumnMapFromSchema(schema));
48
+ this.#schemas = {
49
+ [TABLE_NAME]: schema
50
+ };
51
+ this.#tables = {
52
+ [TABLE_NAME]: table
53
+ };
54
+ this.permissionFunctions = {
55
+ [TABLE_NAME]: permissionFunction
56
+ };
57
+ this.#provider = providerFactory(table);
58
+ this.#services = {
59
+ [TABLE_NAME]: serviceFactory(table, this)
60
+ };
61
+ }
62
+ beforeCreateDataSource() {
63
+ this.start();
64
+ }
65
+ get isRunning() {
66
+ return this.#running;
67
+ }
68
+ start() {
69
+ if (!this.#running) {
70
+ this.#running = true;
71
+ this.#provider.start();
72
+ }
73
+ }
74
+ stop() {
75
+ if (this.#running) {
76
+ this.#running = false;
77
+ this.#provider.stop();
78
+ }
79
+ }
80
+ get menus() {}
81
+ get menuServices() {}
82
+ get schemas() {
83
+ return this.#schemas;
84
+ }
85
+ get services() {
86
+ return this.#services;
87
+ }
88
+ get tables() {
89
+ return this.#tables;
90
+ }
91
+ get visualLinks() {}
92
+ }
93
+ export { NotificationModule };
@@ -0,0 +1,52 @@
1
+ const VuuColumnDataTypes = {
2
+ boolean: "boolean",
3
+ char: "char",
4
+ double: "double",
5
+ epochtimestamp: "epochtimestamp",
6
+ epochtimestampnano: "epochtimestampnano",
7
+ int: "int",
8
+ long: "long",
9
+ string: "string"
10
+ };
11
+ const toColumn = (columnDefinition)=>{
12
+ if ("string" == typeof columnDefinition) {
13
+ const [name, dataType = ""] = columnDefinition.split(":");
14
+ const serverDataType = VuuColumnDataTypes[dataType.toLowerCase()];
15
+ if (name && serverDataType) return {
16
+ name,
17
+ serverDataType
18
+ };
19
+ throw Error(`[NotificationsSchema] invalid column definition '${columnDefinition}'`);
20
+ }
21
+ return columnDefinition;
22
+ };
23
+ const Id = "id:String";
24
+ const Type = "type:String";
25
+ const ExpiryTime = "expiryTime:EpochTimestamp";
26
+ const Title = "title:String";
27
+ const Message = "message:String";
28
+ const Level = "level:String";
29
+ const Audience = "audience:String";
30
+ const genericColumns = [
31
+ Id,
32
+ Type,
33
+ ExpiryTime,
34
+ Title,
35
+ Message,
36
+ Level,
37
+ Audience
38
+ ];
39
+ const NotificationsSchema = {
40
+ Id: Id,
41
+ Type: Type,
42
+ ExpiryTime: ExpiryTime,
43
+ Title: Title,
44
+ Message: Message,
45
+ Level: Level,
46
+ Audience: Audience,
47
+ allFrom: (...additionalColumns)=>[
48
+ ...genericColumns,
49
+ ...additionalColumns
50
+ ].map(toColumn)
51
+ };
52
+ export { NotificationsSchema, toColumn };
@@ -0,0 +1,6 @@
1
+ export * from "./NotificationModule.js";
2
+ export * from "./NotificationsSchema.js";
3
+ export * from "./simulated/DismissNotificationRpcHandler.js";
4
+ export * from "./simulated/SimulatedNotificationsModule.js";
5
+ export * from "./simulated/SimulatedNotificationsProvider.js";
6
+ export * from "./simulated/notificationsModule.js";
@@ -0,0 +1,36 @@
1
+ const errorResult = (errorMessage)=>({
2
+ type: "ERROR_RESULT",
3
+ errorMessage
4
+ });
5
+ const DismissNotificationRpcHandler = (table, module)=>{
6
+ const dismissNotification = async (rpcRequest)=>{
7
+ if ("VIEWPORT_CONTEXT" !== rpcRequest.context.type) return errorResult("dismissNotification requires a viewport context");
8
+ const { dataSource, table: viewportTable, user } = module.getSubscriptionByViewport(rpcRequest.context.viewPortId);
9
+ const selectedRowIds = dataSource.getSelectedRowIds();
10
+ const selection = selectedRowIds.includes("*") ? viewportTable.data.map((row)=>row[viewportTable.map[viewportTable.schema.key]]) : selectedRowIds;
11
+ const { map } = table;
12
+ for (const rowKey of selection){
13
+ const row = table.findByKey(rowKey);
14
+ if (row) {
15
+ const updatedRow = row.slice();
16
+ updatedRow[map.status] = "dismissed";
17
+ updatedRow[map.dismissedBy] = user.name;
18
+ table.updateRow(updatedRow);
19
+ }
20
+ }
21
+ return {
22
+ type: "SUCCESS_RESULT",
23
+ data: {
24
+ success: true,
25
+ dismissedCount: selection.length
26
+ }
27
+ };
28
+ };
29
+ return [
30
+ {
31
+ rpcName: "dismissNotification",
32
+ service: dismissNotification
33
+ }
34
+ ];
35
+ };
36
+ export { DismissNotificationRpcHandler };
@@ -0,0 +1,14 @@
1
+ import { PermissionFilter } from "../../core/filter/PermissionFilter.js";
2
+ import { NotificationModule } from "../NotificationModule.js";
3
+ import { DismissNotificationRpcHandler } from "./DismissNotificationRpcHandler.js";
4
+ import { SimulatedNotificationsProvider } from "./SimulatedNotificationsProvider.js";
5
+ const SimulatedNotificationsModule = ()=>new NotificationModule((table)=>new SimulatedNotificationsProvider(table), (viewport)=>{
6
+ const currentUser = viewport.user.name;
7
+ return PermissionFilter("audience", new Set([
8
+ "all",
9
+ currentUser,
10
+ "admin",
11
+ "trader"
12
+ ]));
13
+ }, DismissNotificationRpcHandler, "source:String", "priority:Int", "status:String", "dismissedBy:String");
14
+ export { SimulatedNotificationsModule };
@@ -0,0 +1,151 @@
1
+ const sampleTemplates = [
2
+ [
3
+ "toast",
4
+ "Order Execution",
5
+ "Order #%04d filled %d shares of AAPL at $%d.%02d",
6
+ "INFO",
7
+ 12000,
8
+ "OMS",
9
+ 2,
10
+ "trader"
11
+ ],
12
+ [
13
+ "toast",
14
+ "Risk Limit Alert",
15
+ "Account ACC-%04d exceeded intraday VaR limit by %d%%",
16
+ "WARNING",
17
+ 15000,
18
+ "RISK",
19
+ 5,
20
+ "risk_manager"
21
+ ],
22
+ [
23
+ "toast",
24
+ "Feed Synchronization",
25
+ "Connected to pricing feed NYC-%02d with latency %dms",
26
+ "INFO",
27
+ 10000,
28
+ "PRICING",
29
+ 1,
30
+ "admin"
31
+ ],
32
+ [
33
+ "toast",
34
+ "Connection Warning",
35
+ "Intermittent packet loss detected on gateway LDN-%02d",
36
+ "WARNING",
37
+ 14000,
38
+ "GATEWAY",
39
+ 3,
40
+ "admin"
41
+ ],
42
+ [
43
+ "toast",
44
+ "Order Rejection",
45
+ "Order #%04d rejected by exchange: Insufficient margin",
46
+ "ERROR",
47
+ 18000,
48
+ "OMS",
49
+ 10,
50
+ "all"
51
+ ],
52
+ [
53
+ "banner",
54
+ "System Maintenance",
55
+ "Scheduled system maintenance will begin in %d minutes",
56
+ "WARNING",
57
+ 45000,
58
+ "SYSTEM",
59
+ 5,
60
+ "all"
61
+ ],
62
+ [
63
+ "banner",
64
+ "Market Status",
65
+ "US Equity markets are now OPEN. Trading session #%d active",
66
+ "INFO",
67
+ 60000,
68
+ "SYSTEM",
69
+ 1,
70
+ "all"
71
+ ]
72
+ ];
73
+ const MAX_ACTIVE_NOTIFICATIONS = 8;
74
+ const INITIAL_NOTIFICATIONS = 3;
75
+ const CYCLE_TIME_MS = 4000;
76
+ const randomInt = (bound)=>Math.floor(Math.random() * bound);
77
+ const formatMessage = (format, ...args)=>{
78
+ let argIndex = 0;
79
+ return format.replace(/%(0(\d+))?d|%%/g, (match, _, width)=>{
80
+ if ("%%" === match) return "%";
81
+ const value = String(args[argIndex++]);
82
+ return width ? value.padStart(Number(width), "0") : value;
83
+ });
84
+ };
85
+ class SimulatedNotificationsProvider {
86
+ #activeNotifications = new Map();
87
+ #table;
88
+ #timer;
89
+ constructor(table){
90
+ this.#table = table;
91
+ }
92
+ start() {
93
+ if (void 0 === this.#timer) {
94
+ for(let i = 0; i < INITIAL_NOTIFICATIONS; i++)this.generateRandomNotification();
95
+ this.#timer = setInterval(this.runOnce, CYCLE_TIME_MS);
96
+ }
97
+ }
98
+ stop() {
99
+ if (void 0 !== this.#timer) {
100
+ clearInterval(this.#timer);
101
+ this.#timer = void 0;
102
+ }
103
+ }
104
+ runOnce = ()=>{
105
+ try {
106
+ const now = Date.now();
107
+ for (const [id, expiryTime] of Array.from(this.#activeNotifications))if (expiryTime < now) {
108
+ this.#table.delete(id);
109
+ this.#activeNotifications.delete(id);
110
+ }
111
+ if (this.#activeNotifications.size < MAX_ACTIVE_NOTIFICATIONS) this.generateRandomNotification();
112
+ } catch (e) {
113
+ console.error("Error occurred in SimulatedNotificationsProvider runOnce", e);
114
+ }
115
+ };
116
+ generateRandomNotification() {
117
+ const id = crypto.randomUUID();
118
+ const now = Date.now();
119
+ const [type, title, messageFormat, level, duration, source, priority, audience] = sampleTemplates[randomInt(sampleTemplates.length)];
120
+ const expiryTime = now + duration;
121
+ const message = messageFormat.includes("%") ? formatMessage(messageFormat, randomInt(9000) + 1000, 10 * randomInt(90) + 100, randomInt(200) + 50, randomInt(99)) : messageFormat;
122
+ const rowData = {
123
+ id,
124
+ type,
125
+ expiryTime,
126
+ title,
127
+ message,
128
+ level,
129
+ audience,
130
+ source,
131
+ priority,
132
+ status: "",
133
+ dismissedBy: "",
134
+ vuuCreatedTimestamp: now,
135
+ vuuUpdatedTimestamp: now,
136
+ vuuMsg: ""
137
+ };
138
+ this.#table.insert(this.toDataRow(rowData));
139
+ this.#activeNotifications.set(id, expiryTime);
140
+ }
141
+ toDataRow(rowData) {
142
+ const { map, schema } = this.#table;
143
+ const row = Array(schema.columns.length);
144
+ for (const [name, value] of Object.entries(rowData)){
145
+ const idx = map[name];
146
+ if (void 0 !== idx) row[idx] = value;
147
+ }
148
+ return row;
149
+ }
150
+ }
151
+ export { SimulatedNotificationsProvider, formatMessage };
@@ -0,0 +1,3 @@
1
+ import { SimulatedNotificationsModule } from "./SimulatedNotificationsModule.js";
2
+ const notificationsModule = SimulatedNotificationsModule();
3
+ export { notificationsModule };
@@ -0,0 +1,41 @@
1
+ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
+ import type { VuuModuleUser } from "../user/CurrentUser";
3
+ import type { ColumnMap } from "@vuu-ui/vuu-utils";
4
+ import type tableContainer from "../table/TableContainer";
5
+ export type TableRow = Array<bigint | VuuRowDataItemType>;
6
+ export type RowPredicate = (row: TableRow) => boolean;
7
+ /**
8
+ * Read access to a single row, by column name.
9
+ */
10
+ export interface RowData {
11
+ get: (columnName: string) => bigint | VuuRowDataItemType | undefined;
12
+ }
13
+ /**
14
+ * Restricts the rows of a table visible through a viewport, typically
15
+ * based on the user that owns the viewport. Permission filters are applied
16
+ * in addition to any client filter.
17
+ */
18
+ export interface PermissionFilter {
19
+ /**
20
+ * Bind this permission filter to the column layout of a table, returning a
21
+ * predicate that can be evaluated against raw table rows.
22
+ */
23
+ createPredicate: (columnMap: ColumnMap) => RowPredicate;
24
+ }
25
+ /**
26
+ * The subset of viewport state available to a PermissionFunction.
27
+ */
28
+ export interface PermissionViewport {
29
+ id: string;
30
+ user: VuuModuleUser;
31
+ }
32
+ export type TableContainer = typeof tableContainer;
33
+ /**
34
+ * Creates the PermissionFilter for a viewport. Configured per table on a VuuModule.
35
+ */
36
+ export type PermissionFunction = (viewport: PermissionViewport, tableContainer: TableContainer) => PermissionFilter;
37
+ export declare const AllowAllPermissionFilter: PermissionFilter;
38
+ export declare const DenyAllPermissionFilter: PermissionFilter;
39
+ export declare function PermissionFilter(rowPredicate: (row: RowData) => boolean): PermissionFilter;
40
+ export declare function PermissionFilter(columnName: string, allowedValues: Set<string>): PermissionFilter;
41
+ export declare function PermissionFilter(filters: Iterable<PermissionFilter>): PermissionFilter;
@@ -0,0 +1,24 @@
1
+ import { Table } from "../../Table";
2
+ import type { RowPredicate } from "./PermissionFilter";
3
+ /**
4
+ * A per-viewport view of a source table, containing only those rows that
5
+ * satisfy a permission predicate. Changes to the source table are mirrored,
6
+ * an update that moves a row into or out of the permitted set is emitted as
7
+ * an insert or delete.
8
+ */
9
+ export declare class PermissionFilteredTable extends Table {
10
+ #private;
11
+ constructor(source: Table, predicate: RowPredicate);
12
+ get source(): Table;
13
+ /**
14
+ * Start mirroring changes from the source table. Invoked on construction,
15
+ * only needs to be called again following dispose.
16
+ */
17
+ attach(): void;
18
+ dispose(): void;
19
+ private contains;
20
+ private handleSourceInsert;
21
+ private handleSourceUpdate;
22
+ private handleSourceDelete;
23
+ private replaceRow;
24
+ }
@@ -2,6 +2,8 @@ import type { DataSourceBase, DataSourceConfig, DataSourceRowWithBigint, DataSou
2
2
  import type { VuuMenu, VuuLink, LinkDescriptorWithLabel, VuuRpcMenuResponse, VuuCreateVisualLink, VuuRemoveVisualLink, VuuRpcServiceRequest, RpcResultSuccess, RpcResultError, VuuRpcMenuRequest } from "@vuu-ui/vuu-protocol-types";
3
3
  import { Table } from "../../Table";
4
4
  import { TickingArrayDataSource } from "../../TickingArrayDataSource";
5
+ import { type PermissionFunction } from "../filter/PermissionFilter";
6
+ import { type VuuModuleUser } from "../user/CurrentUser";
5
7
  export interface IVuuModule<T extends string = string> {
6
8
  createDataSource: (tableName: T, viewport?: string, config?: DataSourceConfig & {
7
9
  session?: SessionDataSourceOverrides;
@@ -28,6 +30,12 @@ type Subscription = {
28
30
  dataSource: DataSourceBase<DataSourceRowWithBigint>;
29
31
  sessionTableName?: string;
30
32
  sourceTableName?: string;
33
+ /**
34
+ * The table viewed by this subscription. Where a permission filter applies,
35
+ * this will be a PermissionFilteredTable
36
+ */
37
+ table: Table;
38
+ user: VuuModuleUser;
31
39
  };
32
40
  export declare abstract class VuuModule<T extends string = string> implements IVuuModule<T> {
33
41
  #private;
@@ -41,6 +49,16 @@ export declare abstract class VuuModule<T extends string = string> implements IV
41
49
  protected get includeDefaultServices(): boolean;
42
50
  protected beforeSessionSave(_sourceTable: Table, _sessionTable: Table): Promise<void>;
43
51
  protected afterSessionSave(_sourceTable: Table, _sessionTable: Table): Promise<void>;
52
+ /**
53
+ * Optional, per table. Restricts the rows visible to each viewport, based on
54
+ * the viewport (and the user who owns it).
55
+ */
56
+ protected permissionFunctions?: Partial<Record<T, PermissionFunction>>;
57
+ /**
58
+ * Invoked before a dataSource is created. Can be used, for example, to
59
+ * lazily start a data provider.
60
+ */
61
+ protected beforeCreateDataSource?(tableName: T): void;
44
62
  getTableSchema(tableName: string): Record<T, Readonly<TableSchema>>[T];
45
63
  getTableList(): string[];
46
64
  protected handlePostSubscribeActivities: ({ clientViewportId, tableSchema, }: DataSourceSubscribedMessage) => void;
@@ -54,6 +72,7 @@ export declare abstract class VuuModule<T extends string = string> implements IV
54
72
  createDataSource: (tableName: T, viewport?: string, config?: DataSourceConfig & {
55
73
  session?: SessionDataSourceOverrides;
56
74
  }) => DataSourceBase<DataSourceRowWithBigint>;
75
+ private createPermissionFilteredTable;
57
76
  getServices(tableName: T): RpcService[];
58
77
  getMenuServices(tableName: T): RpcMenuService[];
59
78
  protected get sessionTableMap(): SessionTableMap;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The user that owns a viewport, as seen by permission functions and
3
+ * rpc services.
4
+ */
5
+ export interface VuuModuleUser {
6
+ authorizations: string[];
7
+ name: string;
8
+ }
9
+ export declare const getCurrentUser: () => VuuModuleUser;
10
+ /**
11
+ * Applies to viewports created after the change. Pass undefined to restore
12
+ * the default user.
13
+ */
14
+ export declare const setCurrentUser: (user?: string | VuuModuleUser) => void;
package/types/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  export * from "./ArrayProxy";
2
+ export * from "./core/filter/PermissionFilter";
3
+ export * from "./core/filter/PermissionFilteredTable";
2
4
  export * from "./core/module/VuuModule";
3
5
  export { ensureVuuModule } from "./core/module/ModuleContainer";
6
+ export * from "./core/user/CurrentUser";
4
7
  export { default as tableContainer } from "./core/table/TableContainer";
5
8
  export * from "./local-datasource-provider/LocalDatasourceProvider";
6
9
  export * from "./makeSuggestions";
10
+ export * from "./notifications";
7
11
  export * from "./schemas";
8
12
  export * from "./simul";
9
13
  export * from "./basket";
@@ -1,13 +1,21 @@
1
+ import { type ManagedModule } from "@heswell/module-admin/contracts";
1
2
  import type { TableSchema } from "@vuu-ui/vuu-data-types";
2
- import { VuuModule } from "../core/module/VuuModule";
3
+ import { VuuModule, type RpcService } from "../core/module/VuuModule";
3
4
  import { type Table } from "../Table";
4
5
  import { type ModuleAdminTableName } from "./module-admin-schemas";
5
6
  import { type ModuleAdminSnapshot } from "./snapshot-projection";
6
7
  type ModuleAdminTables = Record<ModuleAdminTableName, Table>;
7
8
  export declare const MODULE_ADMIN_INITIAL_SNAPSHOT: ModuleAdminSnapshot;
9
+ /**
10
+ * In-browser MODULE_DISCOVERY server. Supports the module admin RPCs from
11
+ * `@heswell/module-admin/contracts` on both tables; state is held in memory.
12
+ */
8
13
  export declare class ModuleAdminModule extends VuuModule<ModuleAdminTableName> {
9
14
  #private;
10
- constructor(snapshot?: ModuleAdminSnapshot);
15
+ private readonly now;
16
+ constructor(snapshot?: ModuleAdminSnapshot, now?: () => number);
17
+ /** Current module state. */
18
+ get modules(): readonly ManagedModule[];
11
19
  get menus(): {
12
20
  modulePermissions: undefined;
13
21
  modules: undefined;
@@ -15,7 +23,8 @@ export declare class ModuleAdminModule extends VuuModule<ModuleAdminTableName> {
15
23
  protected get includeDefaultServices(): boolean;
16
24
  get menuServices(): undefined;
17
25
  get schemas(): Record<ModuleAdminTableName, Readonly<TableSchema>>;
18
- get services(): undefined;
26
+ private service;
27
+ get services(): Record<ModuleAdminTableName, RpcService[]>;
19
28
  get tables(): ModuleAdminTables;
20
29
  get visualLinks(): undefined;
21
30
  }
@@ -1,4 +1,4 @@
1
1
  export { ModuleAdminModule, MODULE_ADMIN_INITIAL_SNAPSHOT, moduleAdminModule, } from "./ModuleAdminModule";
2
2
  export { MODULE_ADMIN_MODULE_NAME, MODULE_ADMIN_TABLE_SCHEMAS, type ModuleAdminTableName, } from "./module-admin-schemas";
3
3
  export { projectModuleAdminSnapshot, reconcileModuleAdminTables, type ModuleAdminSnapshot, } from "./snapshot-projection";
4
- export type { ModuleAccessRole, ModuleDefinition, ModulePermissionRow, ModuleRow, } from "@heswell/module-admin/contracts";
4
+ export type { ManagedModule, ModuleAccessRole, ModuleConfig, ModuleDefinition, ModulePermissionRow, ModuleRow, } from "@heswell/module-admin/contracts";
@@ -1,14 +1,13 @@
1
- import { type ModuleAccessRole, type ModuleDefinition } from "@heswell/module-admin/contracts";
1
+ import { type ManagedModule } from "@heswell/module-admin/contracts";
2
2
  import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
3
3
  import type { Table } from "../Table";
4
4
  import type { ModuleAdminTableName } from "./module-admin-schemas";
5
5
  type Row = Array<bigint | VuuRowDataItemType>;
6
6
  type Tables = Record<ModuleAdminTableName, Table>;
7
7
  export type ModuleAdminSnapshot = {
8
- moduleAccessRoles: readonly ModuleAccessRole[];
9
- modules: readonly ModuleDefinition[];
10
- timestamp: number;
8
+ modules: readonly ManagedModule[];
11
9
  };
12
10
  export declare const projectModuleAdminSnapshot: (snapshot: ModuleAdminSnapshot, tables: Tables) => Record<ModuleAdminTableName, Row[]>;
11
+ /** Brings the tables into line with the snapshot, emitting row-level updates. */
13
12
  export declare const reconcileModuleAdminTables: (snapshot: ModuleAdminSnapshot, tables: Tables) => void;
14
13
  export {};
@@ -0,0 +1,39 @@
1
+ import type { TableSchema } from "@vuu-ui/vuu-data-types";
2
+ import { type RpcService, VuuModule } from "../core/module/VuuModule";
3
+ import type { PermissionFunction } from "../core/filter/PermissionFilter";
4
+ import { type Table } from "../Table";
5
+ import { type ColumnDefinition } from "./NotificationsSchema";
6
+ export type NotificationsTableName = "notifications";
7
+ /**
8
+ * Source of notifications. Started when the first dataSource is
9
+ * created for the notifications table, or explicitly via module.start().
10
+ */
11
+ export interface NotificationsProvider {
12
+ start: () => void;
13
+ stop: () => void;
14
+ }
15
+ export type NotificationsProviderFactory = (table: Table) => NotificationsProvider;
16
+ export type NotificationsServiceFactory = (table: Table, module: NotificationModule) => RpcService[];
17
+ /**
18
+ * A generic Notifications module, exposing a single 'notifications' table.
19
+ * The source of notifications (provider), the rows visible to each user
20
+ * (permissionFunction) and any rpc services (serviceFactory) are supplied
21
+ * by the consumer. Any additional columns are appended to the generic
22
+ * notification columns (see NotificationsSchema).
23
+ */
24
+ export declare class NotificationModule extends VuuModule<NotificationsTableName> {
25
+ #private;
26
+ static readonly NAME = "NOTIFICATIONS";
27
+ static readonly TABLE_NAME: "notifications";
28
+ constructor(providerFactory: NotificationsProviderFactory, permissionFunction: PermissionFunction, serviceFactory: NotificationsServiceFactory, ...additionalColumns: ColumnDefinition[]);
29
+ protected beforeCreateDataSource(): void;
30
+ get isRunning(): boolean;
31
+ start(): void;
32
+ stop(): void;
33
+ get menus(): undefined;
34
+ get menuServices(): undefined;
35
+ get schemas(): Record<"notifications", TableSchema>;
36
+ get services(): Record<"notifications", RpcService[]>;
37
+ get tables(): Record<"notifications", Table>;
38
+ get visualLinks(): undefined;
39
+ }
@@ -0,0 +1,20 @@
1
+ import type { SchemaColumn } from "@vuu-ui/vuu-data-types";
2
+ /**
3
+ * Accepts either a SchemaColumn or a column definition in the Scala server
4
+ * 'name:Type' format, e.g. "source:String", "priority:Int".
5
+ */
6
+ export type ColumnDefinition = SchemaColumn | string;
7
+ export declare const toColumn: (columnDefinition: ColumnDefinition) => SchemaColumn;
8
+ export declare const NotificationsSchema: {
9
+ Id: string;
10
+ Type: string;
11
+ ExpiryTime: string;
12
+ Title: string;
13
+ Message: string;
14
+ Level: string;
15
+ Audience: string;
16
+ /**
17
+ * The generic notification columns, plus any additional columns.
18
+ */
19
+ allFrom: (...additionalColumns: ColumnDefinition[]) => SchemaColumn[];
20
+ };
@@ -0,0 +1,6 @@
1
+ export * from "./NotificationModule";
2
+ export * from "./NotificationsSchema";
3
+ export * from "./simulated/DismissNotificationRpcHandler";
4
+ export * from "./simulated/SimulatedNotificationsModule";
5
+ export * from "./simulated/SimulatedNotificationsProvider";
6
+ export * from "./simulated/notificationsModule";
@@ -0,0 +1,8 @@
1
+ import type { RpcService } from "../../core/module/VuuModule";
2
+ import type { Table } from "../../Table";
3
+ import type { NotificationModule } from "../NotificationModule";
4
+ /**
5
+ * Marks the notifications selected in the requesting viewport as dismissed
6
+ * by the user that owns the viewport.
7
+ */
8
+ export declare const DismissNotificationRpcHandler: (table: Table, module: NotificationModule) => RpcService[];
@@ -0,0 +1,2 @@
1
+ import { NotificationModule } from "../NotificationModule";
2
+ export declare const SimulatedNotificationsModule: () => NotificationModule;
@@ -0,0 +1,21 @@
1
+ import type { Table } from "../../Table";
2
+ import type { NotificationsProvider } from "../NotificationModule";
3
+ /**
4
+ * Minimal implementation of the subset of Java String.format used by
5
+ * the message templates: %d, %0nd and %%. Args are consumed in order.
6
+ */
7
+ export declare const formatMessage: (format: string, ...args: number[]) => string;
8
+ /**
9
+ * Generates random notifications. Seeds an initial set of notifications on
10
+ * start then, on each cycle, deletes expired notifications and adds a new
11
+ * notification if fewer than the maximum are active.
12
+ */
13
+ export declare class SimulatedNotificationsProvider implements NotificationsProvider {
14
+ #private;
15
+ constructor(table: Table);
16
+ start(): void;
17
+ stop(): void;
18
+ runOnce: () => void;
19
+ private generateRandomNotification;
20
+ private toDataRow;
21
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Simulated notifications. The provider starts generating notifications
3
+ * when the first dataSource is created for the notifications table.
4
+ */
5
+ export declare const notificationsModule: import("..").NotificationModule;