logo logo
Design: Database: Members meta

Database Members
Purpose Track the members in the engagement.
Cardinality one Members database in the engagement
one NextMemberItem per engagement
one MemberItem per member in the engagement
Source Code
Items src/ts/dbitems/Members.ts
Zod Models
item MembersItem - union of NextMemberItem, MemberItem below
item.item one of NextMemberRecord, MemberRecord
itemId nextmember
item: NextMemberRecord
    kind 'nextmember'
    nextmnum mnum to assign to next member
itemId number assigned to member. first (host) is 1.
item: MemberRecord
    kind 'member'
    mnum number assigned to member (same as itemId)
    role 'host', 'guest' or 'removed'
    userid userbase userid, saved from when member's user is signed up
        user dbid of member's User database


The Members database is created when the host creates a new engagement. It holds one item to track the next member number and also one item for each guest invited to the engagement that has not been subsequently removed.

(see userbase-js/types/index.d.ts)

  export interface Item {              userbase item
    itemId: string                    ╭────────────────────────┐
    item: any ────────────────────┬──▶│ kind: 'member'         │
    createdBy: Attribution        │   │ mnum: 1                │
    updatedBy?: Attribution       │   │ ...                    │
    fileId?: string               │   ╰────────────────────────┘
    fileName?: string             │
    fileSize?: number             │
    fileUploadedBy?: Attribution  │   ╭────────────────────────┐
    writeAccess?: AccessControl   └──▶│ kind: 'nextmember'     │
  }                                   │ nextmnum: 2            │

MembersRecord represents the specific type of Item.item for an entry
in the Members database - either NextMemberRecord or MemberRecord.


Securepub queries the Members database in order to determine the members of the engagement and find the dbids of each member's User database.

           one per              one per
           member               engagement
   AppId ╶╶╶╶╶╶╶╶╶╶╶► ULID-Role ╶╶╶╶╶╶╶╶╶╶╶► Members
    +                 database               database
   RoleID                                      ╵
                                               └╶╶╶╶╶╶► User

Securepub queries the Members database to derive Userbase usernames for the members in the engagement with the following procedure:

1. selecting the database from getDatabases()
   whose databaseId matches dbids.user

2. searching the database's users array for an object
   with isOwner = true and

3. selecting the corresponding username from that object

    ╭─────────────╮     ╭──────────────────────╮
    │ Members     │     │ getDatabases         │
    ├─────────────┤     ├──────────────────────┤
    │ dbids.user  │╶╶╶╶►│ databaseId (User)    │
    ╰─────────────╯     │ users                │
                        │    ╶╶► isOwner true  │
                        │        username      │

Securepub records but does not directly use member Userbase userids.
Userids are visible to the host and can be used in the Userbase Admin interface.


The Members database holds one NextMemberItem which keeps track of the next "mnum" (member number) which will be assigned to the next member invited by the host.

Member numbers are monotonically increasing integers starting with 1.


The Members database holds one MemberItem for each invited member.

Ownership and Access

The engagement host is the owner of the Members database.

Only the host may write to the Members database.

The Members database is shared readable with all members.

Zod models

The following Zod models show how the Userbase items are represented in memory.

Note that in addition to the values specified by the Userbase Item interface the kind and database attributes are added during deserialization.

export type MembersItem =
  | NextMemberItem
  | MemberItem
export const MembersItem = z.discriminatedUnion('kind', [


export type  NextMemberItem = z.infer<typeof NextMemberItem>;
export const NextMemberItem = z.object({
  kind:                 z.literal('nextmemberitem'),
  itemId:               z.string(),
  item:                 NextMemberRecord,
  updatedBy:            z.custom<Attribution>(),
  database:             z.custom<Database>()
 * NextMemberRecord
export type  NextMemberRecord = z.infer<typeof NextMemberRecord>;
export const NextMemberRecord = z.object({
  kind:                 z.literal('nextmember'),
  nextbnum:             z.number()


export type  MemberItem = z.infer<typeof MemberItem>;
export const MemberItem = z.object({
  kind:                 z.literal('memberitem'),
  itemId:               z.string(),
  item:                 MemberRecord,
  updatedBy:            z.custom<Attribution>(),
  database:             z.custom<Database>()
export type  MemberRecord = z.infer<typeof MemberRecord>;
export const MemberRecord = z.object({
  kind:                 z.literal('member'),
  mnum:                 z.number(),
  role:                 z.union([
  userid:               z.string(),
  dbids:                z.object({
    user:                 z.string()