Group Inheritance
Groups can inherit members from other groups using the extend
method.
When a group extends another group, members of the parent group will become automatically part of the child group.
Basic Usage
Here's how to extend a group:
const
const playlistGroup: Group
playlistGroup =class Group
Group.create(); const
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const trackGroup: Group
trackGroup =class Group
Group.create(); // This way track becomes visible to the members of playlist
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const trackGroup: Group
trackGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const playlistGroup: Group
playlistGroup);
When you extend a group:
- Members of the parent group get access to the child group
- Their roles are inherited (with some exceptions, see below)
- Removing a member from the parent group also removes their access to child groups
Inheriting members but overriding their role
In some cases you might want to inherit all members from a parent group but override/flatten their roles to the same specific role in the child group. You can do so by passing an "override role" as a second argument to extend
:
const
const organizationGroup: Group
organizationGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const organizationGroup: Group
organizationGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(bob, "admin"); const
const bob: Account | ({ [x: string]: any; } & Account)
const billingGroup: Group
billingGroup =class Group
Group.create(); // This way the members of the organization can only read the billing data
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const billingGroup: Group
billingGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const organizationGroup: Group
organizationGroup, "reader");
The "override role" works in both directions:
const
const parentGroup: Group
parentGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const parentGroup: Group
parentGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(bob, "reader");
const bob: Account | ({ [x: string]: any; } & Account)
const parentGroup: Group
parentGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(alice, "admin"); const
const alice: Account | ({ [x: string]: any; } & Account)
const childGroup: Group
childGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const parentGroup: Group
parentGroup, "writer"); // Bob and Alice are now writers in the child group
Multiple Levels of Inheritance
Groups can be extended multiple levels deep:
const
const grandParentGroup: Group
grandParentGroup =class Group
Group.create(); const
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const parentGroup: Group
parentGroup =class Group
Group.create(); const
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const parentGroup: Group
parentGroup);const parentGroup: Group
parentGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const grandParentGroup: Group
grandParentGroup);
Members of the grandparent group will get access to all descendant groups based on their roles.
Permission Changes
When you remove a member from a parent group, they automatically lose access to all child groups. We handle key rotation automatically to ensure security.
// Remove member from parent await
const parentGroup: Group
parentGroup.Group.removeMember(member: Everyone | Account): Promise<void>
removeMember(bob); // Bob loses access to both parent and child groups
const bob: Account | ({ [x: string]: any; } & Account)
Role Inheritance Rules
If the account is already a member of the child group, it will get the more permissive role:
const
const parentGroup: Group
parentGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const parentGroup: Group
parentGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(bob, "reader"); const
const bob: Account | ({ [x: string]: any; } & Account)
const childGroup: Group
childGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const parentGroup: Group
parentGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(bob, "writer");
const bob: Account | ({ [x: string]: any; } & Account)
const childGroup: Group
childGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const parentGroup: Group
parentGroup); // Bob stays a writer because his role is higher // than the inherited reader role.
When extending groups, only admin, writer and reader roles are inherited:
const
const parentGroup: Group
parentGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const parentGroup: Group
parentGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(bob, "writeOnly"); const
const bob: Account | ({ [x: string]: any; } & Account)
const childGroup: Group
childGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const parentGroup: Group
parentGroup); // Bob does not become a member of the child group
To extend a group:
- The current account must be an admin in the child group
- The current account must be a member of the parent group
const
const companyGroup: Group
companyGroup =company.
const company: { name: string; } & CoMap
CoValueBase._owner: Account | Group
_owner.CoValueBase.castAs<typeof Group>(cl: typeof Group): Group
castAs(class Group
Group) constconst teamGroup: Group
teamGroup =class Group
Group.create(); // Works only if I'm a member of companyGroup
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const teamGroup: Group
teamGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const companyGroup: Group
companyGroup);
Revoking a group extension
You can revoke a group extension by using the revokeExtend
method:
const
const parentGroup: Group
parentGroup =class Group
Group.create(); const
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const parentGroup: Group
parentGroup); // Revoke the extension awaitconst childGroup: Group
childGroup.Group.revokeExtend(parent: Group): Promise<Group>
revokeExtend(const parentGroup: Group
parentGroup);
Getting all parent groups
You can get all the parent groups of a group by calling the getParentGroups
method:
const
const childGroup: Group
childGroup =class Group
Group.create(); const
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const parentGroup: Group
parentGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const childGroup: Group
childGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const parentGroup: Group
parentGroup);var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(const childGroup: Group
childGroup.Group.getParentGroups(): Array<Group>
getParentGroups()); // [parentGroup]
Example: Team Hierarchy
Here's a practical example of using group inheritance for team permissions:
// Company-wide group const
const companyGroup: Group
companyGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const companyGroup: Group
companyGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(CEO, "admin"); // Team group with elevated permissions const
const CEO: Account | ({ [x: string]: any; } & Account)
const teamGroup: Group
teamGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const teamGroup: Group
teamGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const companyGroup: Group
companyGroup); // Inherits company-wide accessconst teamGroup: Group
teamGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(teamLead, "admin");
const teamLead: Account | ({ [x: string]: any; } & Account)
const teamGroup: Group
teamGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(developer, "writer"); // Project group with specific permissions const
const developer: Account | ({ [x: string]: any; } & Account)
const projectGroup: Group
projectGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const projectGroup: Group
projectGroup.Group.extend(parent: Group, roleMapping?: "reader" | "writer" | "admin" | "inherit"): Group
extend(const teamGroup: Group
teamGroup); // Inherits team permissionsconst projectGroup: Group
projectGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(client, "reader"); // Client can only read project items
const client: Account | ({ [x: string]: any; } & Account)
This creates a hierarchy where:
- The CEO has admin access to everything
- Team members get writer access to team and project content
- Team leads get admin access to team and project content
- The client can only read project content