@vuu-ui/vuu-data-test 4.0.0-alpha.2 → 4.0.0-alpha.20

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 (51) hide show
  1. package/README.md +8 -0
  2. package/package.json +11 -9
  3. package/src/Table.js +5 -5
  4. package/src/core/filter/PermissionFilter.js +60 -0
  5. package/src/core/filter/PermissionFilteredTable.js +59 -0
  6. package/src/core/module/ModuleContainer.js +8 -0
  7. package/src/core/module/VuuModule.js +54 -6
  8. package/src/core/table/TableContainer.js +1 -1
  9. package/src/core/user/CurrentUser.js +13 -0
  10. package/src/index.js +7 -0
  11. package/src/local-datasource-provider/LocalDatasourceProvider.js +53 -43
  12. package/src/module-admin/ModuleAdminModule.js +124 -0
  13. package/src/module-admin/index.js +3 -0
  14. package/src/module-admin/module-admin-schemas.js +107 -0
  15. package/src/module-admin/snapshot-projection.js +29 -0
  16. package/src/notifications/NotificationModule.js +93 -0
  17. package/src/notifications/NotificationsSchema.js +52 -0
  18. package/src/notifications/index.js +6 -0
  19. package/src/notifications/simulated/DismissNotificationRpcHandler.js +36 -0
  20. package/src/notifications/simulated/SimulatedNotificationsModule.js +14 -0
  21. package/src/notifications/simulated/SimulatedNotificationsProvider.js +151 -0
  22. package/src/notifications/simulated/notificationsModule.js +3 -0
  23. package/src/schemas.js +8 -2
  24. package/src/user-admin/UserAdminModule.js +567 -0
  25. package/src/user-admin/index.js +3 -0
  26. package/src/user-admin/initialSnapshot.js +461 -0
  27. package/src/user-admin/snapshot-projection.js +151 -0
  28. package/types/core/filter/PermissionFilter.d.ts +41 -0
  29. package/types/core/filter/PermissionFilteredTable.d.ts +24 -0
  30. package/types/core/module/ModuleContainer.d.ts +6 -3
  31. package/types/core/module/VuuModule.d.ts +22 -0
  32. package/types/core/table/TableContainer.d.ts +5 -5
  33. package/types/core/user/CurrentUser.d.ts +14 -0
  34. package/types/index.d.ts +7 -0
  35. package/types/local-datasource-provider/LocalDatasourceProvider.d.ts +15 -1
  36. package/types/module-admin/ModuleAdminModule.d.ts +32 -0
  37. package/types/module-admin/index.d.ts +4 -0
  38. package/types/module-admin/module-admin-schemas.d.ts +4 -0
  39. package/types/module-admin/snapshot-projection.d.ts +13 -0
  40. package/types/notifications/NotificationModule.d.ts +39 -0
  41. package/types/notifications/NotificationsSchema.d.ts +20 -0
  42. package/types/notifications/index.d.ts +6 -0
  43. package/types/notifications/simulated/DismissNotificationRpcHandler.d.ts +8 -0
  44. package/types/notifications/simulated/SimulatedNotificationsModule.d.ts +2 -0
  45. package/types/notifications/simulated/SimulatedNotificationsProvider.d.ts +21 -0
  46. package/types/notifications/simulated/notificationsModule.d.ts +5 -0
  47. package/types/schemas.d.ts +5 -3
  48. package/types/user-admin/UserAdminModule.d.ts +54 -0
  49. package/types/user-admin/index.d.ts +3 -0
  50. package/types/user-admin/initialSnapshot.d.ts +2 -0
  51. package/types/user-admin/snapshot-projection.d.ts +9 -0
@@ -0,0 +1,107 @@
1
+ import { VUU_DEFAULT_COLUMNS } from "../default-column-definitions.js";
2
+ const DEFAULT_RANGE_LIMITS = {
3
+ maxRangeEnd: 1000000,
4
+ maxRangeWidth: 1000
5
+ };
6
+ const MODULE_ADMIN_MODULE_NAME = "MODULE_DISCOVERY";
7
+ const MODULE_ADMIN_TABLE_SCHEMAS = {
8
+ modules: {
9
+ columns: [
10
+ {
11
+ name: "id",
12
+ serverDataType: "int"
13
+ },
14
+ {
15
+ name: "parentModuleId",
16
+ serverDataType: "int"
17
+ },
18
+ {
19
+ name: "name",
20
+ serverDataType: "string"
21
+ },
22
+ {
23
+ name: "title",
24
+ serverDataType: "string"
25
+ },
26
+ {
27
+ name: "description",
28
+ serverDataType: "string"
29
+ },
30
+ {
31
+ name: "version",
32
+ serverDataType: "int"
33
+ },
34
+ {
35
+ name: "enabled",
36
+ serverDataType: "boolean"
37
+ },
38
+ {
39
+ name: "location",
40
+ serverDataType: "string"
41
+ },
42
+ {
43
+ name: "path",
44
+ serverDataType: "string"
45
+ },
46
+ {
47
+ name: "mfComponent",
48
+ serverDataType: "string"
49
+ },
50
+ {
51
+ name: "mfScope",
52
+ serverDataType: "string"
53
+ },
54
+ {
55
+ name: "mfUrl",
56
+ serverDataType: "string"
57
+ },
58
+ {
59
+ name: "vuuConnectionId",
60
+ serverDataType: "string"
61
+ },
62
+ {
63
+ name: "vuuWebsocketUrl",
64
+ serverDataType: "string"
65
+ },
66
+ {
67
+ name: "vuuRestUrl",
68
+ serverDataType: "string"
69
+ },
70
+ {
71
+ name: "navIconUrl",
72
+ serverDataType: "string"
73
+ },
74
+ ...VUU_DEFAULT_COLUMNS
75
+ ],
76
+ key: "id",
77
+ rangeLimits: DEFAULT_RANGE_LIMITS,
78
+ table: {
79
+ module: MODULE_ADMIN_MODULE_NAME,
80
+ table: "modules"
81
+ }
82
+ },
83
+ modulePermissions: {
84
+ columns: [
85
+ {
86
+ name: "id",
87
+ serverDataType: "int"
88
+ },
89
+ {
90
+ name: "module_id",
91
+ serverDataType: "int"
92
+ },
93
+ {
94
+ name: "role",
95
+ serverDataType: "string"
96
+ },
97
+ ...VUU_DEFAULT_COLUMNS
98
+ ],
99
+ key: "id",
100
+ rangeLimits: DEFAULT_RANGE_LIMITS,
101
+ table: {
102
+ module: MODULE_ADMIN_MODULE_NAME,
103
+ table: "modulePermissions"
104
+ }
105
+ }
106
+ };
107
+ export { MODULE_ADMIN_MODULE_NAME, MODULE_ADMIN_TABLE_SCHEMAS };
@@ -0,0 +1,29 @@
1
+ import { managedModuleColumnValues, managedModulePermissionValues } from "@heswell/module-admin/contracts";
2
+ const rowsEqual = (left, right)=>left.length === right.length && left.every((value, index)=>value === right[index]);
3
+ const toRow = (table, values)=>table.schema.columns.map(({ name })=>values[name] ?? "");
4
+ const projectModuleAdminSnapshot = (snapshot, tables)=>({
5
+ modulePermissions: managedModulePermissionValues(snapshot.modules).map((values)=>toRow(tables.modulePermissions, values)),
6
+ modules: snapshot.modules.map((module)=>toRow(tables.modules, managedModuleColumnValues(module)))
7
+ });
8
+ const reconcileModuleAdminTables = (snapshot, tables)=>{
9
+ const projected = projectModuleAdminSnapshot(snapshot, tables);
10
+ for (const tableName of Object.keys(tables)){
11
+ const table = tables[tableName];
12
+ const desiredRows = projected[tableName];
13
+ const keyIndex = table.map[table.schema.key];
14
+ const desiredKeys = new Set(desiredRows.map((row)=>String(row[keyIndex])));
15
+ for (const existingRow of [
16
+ ...table.data
17
+ ]){
18
+ const key = String(existingRow[keyIndex]);
19
+ if (!desiredKeys.has(key)) table.delete(key);
20
+ }
21
+ for (const row of desiredRows){
22
+ const key = String(row[keyIndex]);
23
+ const existingRow = table.findByKey(key);
24
+ if (void 0 === existingRow) table.insert(row);
25
+ else if (!rowsEqual(existingRow, row)) table.updateRow(row);
26
+ }
27
+ }
28
+ };
29
+ export { projectModuleAdminSnapshot, reconcileModuleAdminTables };
@@ -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 };
package/src/schemas.js CHANGED
@@ -1,15 +1,21 @@
1
1
  import { schemas } from "./basket/basket-schemas.js";
2
2
  import { schemas as simul_schemas_js_schemas } from "./simul/simul-schemas.js";
3
3
  import { testModule } from "./test/TestModule.js";
4
+ import { USER_ADMIN_TABLE_SCHEMAS } from "@heswell/user-admin/contracts";
5
+ import { MODULE_ADMIN_TABLE_SCHEMAS } from "./module-admin/index.js";
4
6
  const schemas_schemas = {
5
7
  ...schemas,
6
8
  ...simul_schemas_js_schemas,
7
- ...testModule.schemas
9
+ ...testModule.schemas,
10
+ ...USER_ADMIN_TABLE_SCHEMAS,
11
+ ...MODULE_ADMIN_TABLE_SCHEMAS
8
12
  };
9
13
  const allSchemas = {
10
14
  ...schemas,
11
15
  ...simul_schemas_js_schemas,
12
- ...testModule.schemas
16
+ ...testModule.schemas,
17
+ ...USER_ADMIN_TABLE_SCHEMAS,
18
+ ...MODULE_ADMIN_TABLE_SCHEMAS
13
19
  };
14
20
  const getAllSchemas = ()=>schemas_schemas;
15
21
  const getSchema = (tableName)=>{