@vuu-ui/vuu-data-test 4.0.0-alpha.3 → 4.0.0-alpha.30

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 (53) hide show
  1. package/README.md +38 -0
  2. package/package.json +10 -9
  3. package/src/TickingArrayDataSource.js +7 -2
  4. package/src/basket/BasketModule.js +105 -40
  5. package/src/basket/basket-schemas.js +16 -0
  6. package/src/basket/reference-data/constituent-data.js +2 -0
  7. package/src/basket/reference-data/constituents.js +6 -74
  8. package/src/basket/reference-data/prices.js +9 -3
  9. package/src/core/filter/PermissionFilter.js +60 -0
  10. package/src/core/filter/PermissionFilteredTable.js +59 -0
  11. package/src/core/module/VuuModule.js +30 -4
  12. package/src/core/table/TableContainer.js +51 -8
  13. package/src/core/user/CurrentUser.js +13 -0
  14. package/src/index.js +4 -0
  15. package/src/local-datasource-provider/LocalDatasourceProvider.js +64 -44
  16. package/src/module-admin/ModuleAdminModule.js +84 -20
  17. package/src/module-admin/module-admin-schemas.js +5 -9
  18. package/src/module-admin/snapshot-projection.js +3 -31
  19. package/src/notifications/NotificationModule.js +131 -0
  20. package/src/notifications/NotificationsSchema.js +52 -0
  21. package/src/notifications/index.js +6 -0
  22. package/src/notifications/simulated/DismissNotificationRpcHandler.js +36 -0
  23. package/src/notifications/simulated/SimulatedNotificationsModule.js +18 -0
  24. package/src/notifications/simulated/SimulatedNotificationsProvider.js +151 -0
  25. package/src/notifications/simulated/notificationsModule.js +3 -0
  26. package/src/user-admin/UserAdminModule.js +163 -8
  27. package/src/user-admin/initialSnapshot.js +49 -65
  28. package/types/TickingArrayDataSource.d.ts +1 -1
  29. package/types/basket/BasketModule.d.ts +2 -0
  30. package/types/basket/reference-data/constituents.d.ts +3 -3
  31. package/types/basket/reference-data/prices.d.ts +2 -1
  32. package/types/core/filter/PermissionFilter.d.ts +41 -0
  33. package/types/core/filter/PermissionFilteredTable.d.ts +24 -0
  34. package/types/core/module/VuuModule.d.ts +19 -0
  35. package/types/core/table/TableContainer.d.ts +1 -1
  36. package/types/core/user/CurrentUser.d.ts +14 -0
  37. package/types/index.d.ts +4 -0
  38. package/types/local-datasource-provider/LocalDatasourceProvider.d.ts +18 -2
  39. package/types/module-admin/ModuleAdminModule.d.ts +12 -3
  40. package/types/module-admin/index.d.ts +1 -1
  41. package/types/module-admin/snapshot-projection.d.ts +3 -4
  42. package/types/notifications/NotificationModule.d.ts +60 -0
  43. package/types/notifications/NotificationsSchema.d.ts +20 -0
  44. package/types/notifications/index.d.ts +6 -0
  45. package/types/notifications/simulated/DismissNotificationRpcHandler.d.ts +8 -0
  46. package/types/notifications/simulated/SimulatedNotificationsModule.d.ts +9 -0
  47. package/types/notifications/simulated/SimulatedNotificationsProvider.d.ts +21 -0
  48. package/types/notifications/simulated/notificationsModule.d.ts +5 -0
  49. package/types/user-admin/UserAdminModule.d.ts +5 -1
  50. package/src/basket/reference-data/ftse100.js +0 -686
  51. package/src/basket/reference-data/hsi.js +0 -1047
  52. package/src/basket/reference-data/nasdaq100.js +0 -812
  53. package/src/basket/reference-data/sp500.js +0 -315
@@ -65,6 +65,19 @@ const parseAssignments = (value)=>{
65
65
  if (!Array.isArray(parsed) || parsed.some((assignment)=>!isRecord(assignment) || "string" != typeof assignment.accessRole || 0 === assignment.accessRole.length || "string" != typeof assignment.groupId || 0 === assignment.groupId.length || Object.keys(assignment).some((name)=>"accessRole" !== name && "groupId" !== name))) throw new Error("assignments must be a JSON array of accessRole and groupId strings");
66
66
  return parsed;
67
67
  };
68
+ const parseSessionRoleAssignments = (value)=>{
69
+ if ("string" != typeof value) throw new Error("role_assignments must be a serialized array");
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(value);
73
+ } catch {
74
+ throw new Error("role_assignments must be a valid JSON array");
75
+ }
76
+ if (!Array.isArray(parsed) || parsed.some((roleId)=>"string" != typeof roleId || !roleId)) throw new Error("role_assignments must be an array of role ID strings");
77
+ return [
78
+ ...new Set(parsed)
79
+ ];
80
+ };
68
81
  const parseSessionPermissions = (value)=>{
69
82
  if ("string" != typeof value) throw new Error("permissions must be a serialized array");
70
83
  let parsed;
@@ -206,17 +219,32 @@ class UserAdminModule extends VuuModule {
206
219
  }
207
220
  createSessionTable(sourceTable, sessionTableName, editSessionMode, dataSource, sessionType) {
208
221
  const sessionTable = super.createSessionTable(sourceTable, sessionTableName, editSessionMode, dataSource, sessionType);
209
- if ("users" !== sourceTable.name) return sessionTable;
210
- const sessionSchema = {
211
- ...sessionTable.schema,
212
- columns: sessionTable.schema.columns.concat({
222
+ const extraColumns = "users" === sourceTable.name ? [
223
+ {
213
224
  name: "permissions",
214
225
  serverDataType: "string"
215
- })
226
+ }
227
+ ] : "groups" === sourceTable.name ? [
228
+ {
229
+ name: "group_name",
230
+ serverDataType: "string"
231
+ },
232
+ {
233
+ name: "role_assignments",
234
+ serverDataType: "string"
235
+ }
236
+ ] : [];
237
+ const columnsToAdd = extraColumns.filter(({ name })=>!sessionTable.schema.columns.some((column)=>column.name === name));
238
+ if (0 === columnsToAdd.length) return sessionTable;
239
+ const sessionSchema = {
240
+ ...sessionTable.schema,
241
+ columns: sessionTable.schema.columns.concat(columnsToAdd)
216
242
  };
217
- return new Table(sessionSchema, sessionTable.data.map((row)=>row.concat("")), buildDataColumnMapFromSchema(sessionSchema));
243
+ return new Table(sessionSchema, sessionTable.data.map((row)=>row.concat(columnsToAdd.map(()=>""))), buildDataColumnMapFromSchema(sessionSchema));
218
244
  }
219
245
  async beforeSessionSave(sourceTable, sessionTable) {
246
+ if ("roles" === sourceTable.name) return void await this.saveClientRolesFromSession(sessionTable);
247
+ if ("groups" === sourceTable.name) return void await this.saveGroupsFromSession(sessionTable);
220
248
  if ("users" !== sourceTable.name) return;
221
249
  const permissionsIndex = sessionTable.map.permissions;
222
250
  const actionIndex = sessionTable.map.vuuAction;
@@ -230,8 +258,135 @@ class UserAdminModule extends VuuModule {
230
258
  if ("string" == typeof row[permissionsIndex] && "" !== row[permissionsIndex]) await this.store.setUserModuleAccess(userId, parseSessionPermissions(row[permissionsIndex]));
231
259
  }
232
260
  }
233
- async afterSessionSave(sourceTable) {
234
- if ("users" === sourceTable.name) await this.reconcile();
261
+ async afterSessionSave(sourceTable, sessionTable) {
262
+ const roleCreationSaved = "roles" === sourceTable.name && sessionTable.data.some((row)=>"addRow" === row[sessionTable.map.vuuAction] && "client role created" === row[sessionTable.map.vuuMsg]);
263
+ const groupChangesSaved = "groups" === sourceTable.name && sessionTable.data.some((row)=>"group created" === row[sessionTable.map.vuuMsg] || "group roles updated" === row[sessionTable.map.vuuMsg]);
264
+ if ("users" === sourceTable.name || roleCreationSaved || groupChangesSaved) await this.reconcile();
265
+ }
266
+ async saveGroupsFromSession(sessionTable) {
267
+ const actionIndex = sessionTable.map.vuuAction;
268
+ const messageIndex = sessionTable.map.vuuMsg;
269
+ const keyIndex = sessionTable.map[sessionTable.schema.key];
270
+ const groupIdIndex = sessionTable.map.group_id;
271
+ const groupNameIndex = sessionTable.map.group_name;
272
+ const roleAssignmentsIndex = sessionTable.map.role_assignments;
273
+ for (const row of sessionTable.data)if ("addRow" !== row[actionIndex] || row[messageIndex]) {
274
+ if ("editCell" === row[actionIndex] && "string" == typeof row[roleAssignmentsIndex] && "" !== row[roleAssignmentsIndex]) {
275
+ const groupId = requiredString({
276
+ group_id: row[groupIdIndex]
277
+ }, "group_id");
278
+ const roleIds = parseSessionRoleAssignments(row[roleAssignmentsIndex]);
279
+ await this.saveGroupRoleAssignments(groupId, roleIds, await this.store.snapshot());
280
+ sessionTable.update(String(row[keyIndex]), "vuuMsg", "group roles updated");
281
+ }
282
+ } else {
283
+ const name = requiredString({
284
+ group_name: row[groupNameIndex]
285
+ }, "group_name").trim();
286
+ if (!name) throw new Error("group_name must be a non-empty string");
287
+ const roleIds = parseSessionRoleAssignments(row[roleAssignmentsIndex]);
288
+ const before = await this.store.snapshot();
289
+ this.resolveGroupRoleAssignments(roleIds, before);
290
+ const existingGroupIds = new Set(before.groups.map(({ id })=>id));
291
+ await this.store.addGroup({
292
+ name
293
+ });
294
+ const snapshot = await this.store.snapshot();
295
+ const group = snapshot.groups.find(({ id, name: groupName })=>!existingGroupIds.has(id) && groupName === name);
296
+ if (!group) throw new Error("Created group could not be found");
297
+ await this.saveGroupRoleAssignments(group.id, roleIds, snapshot);
298
+ sessionTable.update(String(row[keyIndex]), "vuuMsg", "group created");
299
+ }
300
+ }
301
+ async saveGroupRoleAssignments(groupId, desiredRoleIds, snapshot) {
302
+ if (!snapshot.groups.some(({ id })=>id === groupId)) throw new Error(`User admin group not found: ${groupId}`);
303
+ const groupRoles = snapshot.groupRoles.filter(({ group })=>group.id === groupId);
304
+ const roleAssignmentsById = new Map(snapshot.clientRoles.map((assignment)=>[
305
+ assignment.role.id,
306
+ assignment
307
+ ]));
308
+ const desiredRoleSet = new Set(desiredRoleIds);
309
+ const currentAssignments = groupRoles.map(({ role })=>{
310
+ const assignment = roleAssignmentsById.get(role.id);
311
+ if (!assignment) throw new Error(`Assigned user admin role no longer exists: ${role.id}`);
312
+ return assignment;
313
+ });
314
+ const currentRoleSet = new Set(groupRoles.map(({ role })=>role.id));
315
+ const desiredAssignments = this.resolveGroupRoleAssignments(desiredRoleIds, snapshot);
316
+ for (const assignment of currentAssignments)if (!desiredRoleSet.has(assignment.role.id)) {
317
+ const clientRef = assignment.role.clientRole && assignment.client?.clientId ? {
318
+ clientKey: assignment.client.clientId
319
+ } : void 0;
320
+ await this.store.removeRoleFromGroup({
321
+ groupId
322
+ }, {
323
+ roleId: assignment.role.id
324
+ }, clientRef);
325
+ }
326
+ for (const assignment of desiredAssignments)if (!currentRoleSet.has(assignment.role.id)) {
327
+ const clientRef = assignment.role.clientRole && assignment.client?.clientId ? {
328
+ clientKey: assignment.client.clientId
329
+ } : void 0;
330
+ await this.store.addRoleToGroup({
331
+ groupId
332
+ }, {
333
+ roleId: assignment.role.id
334
+ }, clientRef);
335
+ }
336
+ }
337
+ resolveGroupRoleAssignments(roleIds, snapshot) {
338
+ const roleAssignmentsById = new Map(snapshot.clientRoles.map((assignment)=>[
339
+ assignment.role.id,
340
+ assignment
341
+ ]));
342
+ return roleIds.map((roleId)=>{
343
+ const assignment = roleAssignmentsById.get(roleId);
344
+ if (!assignment) throw new Error(`User admin role not found: ${roleId}`);
345
+ if (assignment.role.clientRole && !assignment.client?.clientId) throw new Error(`User admin client role has no owning client: ${roleId}`);
346
+ return assignment;
347
+ });
348
+ }
349
+ async saveClientRolesFromSession(sessionTable) {
350
+ const actionIndex = sessionTable.map.vuuAction;
351
+ const messageIndex = sessionTable.map.vuuMsg;
352
+ const keyIndex = sessionTable.map[sessionTable.schema.key];
353
+ const snapshot = await this.store.snapshot();
354
+ const clientsById = new Map(snapshot.clients.map((client)=>[
355
+ client.id,
356
+ client
357
+ ]));
358
+ const pendingRoles = [
359
+ ...sessionTable.data
360
+ ].flatMap((row)=>{
361
+ if ("addRow" !== row[actionIndex] || row[messageIndex]) return [];
362
+ const params = Object.fromEntries(sessionTable.schema.columns.map(({ name })=>[
363
+ name,
364
+ row[sessionTable.map[name]]
365
+ ]));
366
+ const roleName = requiredString(params, "role_name").trim();
367
+ if (!roleName) throw new Error("role_name must be a non-empty string");
368
+ const clientId = requiredString(params, "client_id");
369
+ const clientIdentifier = requiredString(params, "client_identifier");
370
+ const client = clientsById.get(clientId);
371
+ if (!client || client.clientId !== clientIdentifier) throw new Error("client_id and client_identifier must identify the same client");
372
+ return [
373
+ {
374
+ client,
375
+ description: optionalString(params, "description") ?? "",
376
+ roleName,
377
+ row
378
+ }
379
+ ];
380
+ });
381
+ for (const { client, description, roleName, row } of pendingRoles){
382
+ await this.store.addClientRole({
383
+ clientKey: client.clientId
384
+ }, {
385
+ description,
386
+ name: roleName
387
+ });
388
+ sessionTable.update(String(row[keyIndex]), "vuuMsg", "client role created");
389
+ }
235
390
  }
236
391
  mutation = (rpcName, mutate)=>async (request)=>{
237
392
  try {
@@ -6,6 +6,34 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
6
6
  enabled: true,
7
7
  id: "client-portal",
8
8
  name: "VUU Portal"
9
+ },
10
+ {
11
+ clientId: "vuu-user-admin",
12
+ description: "User administration",
13
+ enabled: true,
14
+ id: "client-user-admin",
15
+ name: "User Admin"
16
+ },
17
+ {
18
+ clientId: "vuu-basket-trading",
19
+ description: "Basket trading",
20
+ enabled: true,
21
+ id: "client-basket-trading",
22
+ name: "Basket Trading"
23
+ },
24
+ {
25
+ clientId: "vuu-module-admin",
26
+ description: "Module administration",
27
+ enabled: true,
28
+ id: "client-module-admin",
29
+ name: "Module Admin"
30
+ },
31
+ {
32
+ clientId: "vuu-feature-filter-table",
33
+ description: "Instrument tables",
34
+ enabled: true,
35
+ id: "client-feature-filter-table",
36
+ name: "Instruments"
9
37
  }
10
38
  ],
11
39
  clientRoles: [
@@ -67,13 +95,13 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
67
95
  },
68
96
  {
69
97
  client: {
70
- clientId: "vuu-portal",
71
- id: "client-portal",
72
- name: "VUU Portal"
98
+ clientId: "vuu-user-admin",
99
+ id: "client-user-admin",
100
+ name: "User Admin"
73
101
  },
74
102
  role: {
75
103
  clientRole: true,
76
- containerId: "client-portal",
104
+ containerId: "client-user-admin",
77
105
  id: "role-user-admin-admin",
78
106
  name: "user-admin-admin",
79
107
  roleDisplayName: "admin"
@@ -81,13 +109,13 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
81
109
  },
82
110
  {
83
111
  client: {
84
- clientId: "vuu-portal",
85
- id: "client-portal",
86
- name: "VUU Portal"
112
+ clientId: "vuu-module-admin",
113
+ id: "client-module-admin",
114
+ name: "Module Admin"
87
115
  },
88
116
  role: {
89
117
  clientRole: true,
90
- containerId: "client-portal",
118
+ containerId: "client-module-admin",
91
119
  id: "role-module-admin-admin",
92
120
  name: "module-admin-admin",
93
121
  roleDisplayName: "admin"
@@ -95,29 +123,17 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
95
123
  },
96
124
  {
97
125
  client: {
98
- clientId: "vuu-portal",
99
- id: "client-portal",
100
- name: "VUU Portal"
126
+ clientId: "vuu-basket-trading",
127
+ id: "client-basket-trading",
128
+ name: "Basket Trading"
101
129
  },
102
130
  role: {
103
131
  clientRole: true,
104
- containerId: "client-portal",
132
+ containerId: "client-basket-trading",
105
133
  id: "role-basket-trading-trade",
106
134
  name: "basket-trading-trade",
107
135
  roleDisplayName: "trade"
108
136
  }
109
- },
110
- {
111
- client: {
112
- clientId: "realm",
113
- id: "realm",
114
- name: "Realm"
115
- },
116
- role: {
117
- id: "role-admin",
118
- name: "admin",
119
- roleDisplayName: "admin"
120
- }
121
137
  }
122
138
  ],
123
139
  groupRoles: [
@@ -225,9 +241,9 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
225
241
  },
226
242
  {
227
243
  client: {
228
- clientId: "vuu-portal",
229
- id: "client-portal",
230
- name: "VUU Portal"
244
+ clientId: "vuu-user-admin",
245
+ id: "client-user-admin",
246
+ name: "User Admin"
231
247
  },
232
248
  group: {
233
249
  id: "group-user-admin-admin",
@@ -261,9 +277,9 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
261
277
  },
262
278
  {
263
279
  client: {
264
- clientId: "vuu-portal",
265
- id: "client-portal",
266
- name: "VUU Portal"
280
+ clientId: "vuu-module-admin",
281
+ id: "client-module-admin",
282
+ name: "Module Admin"
267
283
  },
268
284
  group: {
269
285
  id: "group-module-admin-admin",
@@ -297,9 +313,9 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
297
313
  },
298
314
  {
299
315
  client: {
300
- clientId: "vuu-portal",
301
- id: "client-portal",
302
- name: "VUU Portal"
316
+ clientId: "vuu-basket-trading",
317
+ id: "client-basket-trading",
318
+ name: "Basket Trading"
303
319
  },
304
320
  group: {
305
321
  id: "group-basket-trading-trade",
@@ -312,19 +328,6 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
312
328
  name: "basket-trading-trade",
313
329
  roleDisplayName: "trade"
314
330
  }
315
- },
316
- {
317
- group: {
318
- id: "group-admins",
319
- name: "administrators",
320
- groupDisplayName: "administrators",
321
- path: "/vuu/administrators"
322
- },
323
- role: {
324
- id: "role-admin",
325
- name: "admin",
326
- roleDisplayName: "admin"
327
- }
328
331
  }
329
332
  ],
330
333
  groups: [
@@ -381,12 +384,6 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
381
384
  name: "basket-trading-trade",
382
385
  groupDisplayName: "trade",
383
386
  path: "/vuu/basket-trading-trade"
384
- },
385
- {
386
- id: "group-admins",
387
- name: "administrators",
388
- groupDisplayName: "administrators",
389
- path: "/vuu/administrators"
390
387
  }
391
388
  ],
392
389
  timestamp: 1710000000000,
@@ -438,19 +435,6 @@ const USER_ADMIN_INITIAL_SNAPSHOT = {
438
435
  id: "user-alice",
439
436
  username: "alice"
440
437
  }
441
- },
442
- {
443
- group: {
444
- id: "group-admins",
445
- name: "administrators",
446
- groupDisplayName: "administrators",
447
- path: "/vuu/administrators"
448
- },
449
- user: {
450
- email: "alice@example.com",
451
- id: "user-alice",
452
- username: "alice"
453
- }
454
438
  }
455
439
  ],
456
440
  users: [
@@ -33,7 +33,7 @@ export declare class TickingArrayDataSource extends ArrayDataSource {
33
33
  undoRowChange: (key: string) => Promise<RpcResultSuccess | RpcResultError>;
34
34
  set columns(columns: string[]);
35
35
  get columns(): string[];
36
- endEditSession(saveChanges?: boolean): Promise<void>;
36
+ endEditSession(saveChanges?: boolean, force?: boolean): Promise<void>;
37
37
  rpcRequest(rpcRequest: Omit<VuuRpcServiceRequest, "context">): Promise<RpcResultSuccess | RpcResultError>;
38
38
  menuRpcCall(rpcRequest: Omit<VuuRpcMenuRequest, "vpId">): Promise<VuuRpcMenuResponse>;
39
39
  getTypeaheadSuggestions(column: string, pattern?: string): Promise<string[]>;
@@ -47,9 +47,11 @@ export declare class BasketModule extends VuuModule<BasketsTableName> {
47
47
  }[];
48
48
  };
49
49
  private createTradingBasket;
50
+ private insertTradingConstituent;
50
51
  private createNewBasket;
51
52
  private sendToMarket;
52
53
  private takeOffMarket;
53
54
  private addConstituent;
55
+ private updateConstituentStatus;
54
56
  }
55
57
  export declare const basketModule: BasketModule;
@@ -1,3 +1,3 @@
1
- import { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
- declare const _default: VuuRowDataItemType[][];
3
- export default _default;
1
+ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
+ declare const basketConstituentData: VuuRowDataItemType[][];
3
+ export default basketConstituentData;
@@ -1,3 +1,4 @@
1
+ import { Table } from "../../Table";
1
2
  export type ask = number;
2
3
  export type askSize = number;
3
4
  export type bid = number;
@@ -20,5 +21,5 @@ export type PricesDataRow = [
20
21
  ric,
21
22
  scenario
22
23
  ];
23
- declare const pricesTable: import("../..").Table;
24
+ declare const pricesTable: Table;
24
25
  export default pricesTable;
@@ -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;
@@ -8,7 +8,7 @@ declare class TableContainer {
8
8
  private constructor();
9
9
  static get instance(): TableContainer;
10
10
  createTable: (schema: TableSchema, data?: Array<Array<VuuRowDataItemType | bigint>>, dataMap?: ColumnMap, updateGenerator?: UpdateGenerator) => Table;
11
- createJoinTable(joinTable: VuuTable, { table: t1 }: VuuTable, { table: t2 }: VuuTable, joinColumn: string): Table;
11
+ createJoinTable(joinTable: VuuTable, source1: VuuTable | Table, source2: VuuTable | Table, joinColumn: string): Table;
12
12
  addTable(table: Table): void;
13
13
  getTable<T = Table>(tableName: string): NonNullable<T>;
14
14
  }
@@ -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,4 +1,20 @@
1
+ import { type LocalVuuServer } from "@vuu-ui/core";
1
2
  import type { ReactNode } from "react";
2
- export declare const LocalDataSourceProvider: ({ children, }: {
3
+ import type { VuuModule } from "../core/module/VuuModule";
4
+ /** Data context covering every VuuModule registered in the browser. */
5
+ export declare const LocalDataSourceProvider: ({ children }: {
3
6
  children: ReactNode;
4
- }) => import("react").JSX.Element;
7
+ }) => import("react/jsx-runtime").JSX.Element;
8
+ export interface LocalVuuServerOptions {
9
+ /** Matches the `vuu.connectionId` of the modules this server serves. */
10
+ connectionId: string;
11
+ modules: VuuModule<any>[];
12
+ }
13
+ /**
14
+ * Simulates a Vuu server in the browser, publishing the tables of the given
15
+ * modules. Module instances are resolved from this server, not by name from
16
+ * the global registry, so servers can each have their own instance of a
17
+ * module (e.g. NOTIFICATIONS). Pass the result to a local-mode `AuthenticationProvider` as one of
18
+ * its `localServers`.
19
+ */
20
+ export declare const createLocalVuuServer: ({ connectionId, modules, }: LocalVuuServerOptions) => LocalVuuServer;
@@ -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";