Repository: PipedreamHQ/pipedream
Pull request #16405: New Components - peerdom
Author: login: luancazarine
Labels: name: ai-assisted
description: Content generated by AI, with human refinement and modification
Created: 2025-04-23T17:21:22Z | Merged: 2025-04-30T15:22:14Z

Resolves #16400.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added actions to assign a member to a role, create a new role, and update an existing role within Peerdom.
  - Introduced event sources to detect and emit events when a new member is added or a new role is created.
  - Implemented comprehensive Peerdom app integration with dynamic property options and full API support for managing groups, maps, roles, and members.

- **Improvements**
  - Added utility functions and constants to support configuration and data parsing.
  - Included sample event data for easier testing and validation of new member and role event sources.

- **Chores**
  - Updated package version and added necessary dependencies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #16400: [Components] peerdom
# peerdom
## URLs
- https://api.peerdom.org/v1/docs
## Polling Sources
### new-role
#### Prompt
Emit new event when a new role is created in a circle (organization). Requires circle ID as a prop.
#### URLs
- https://api.peerdom.org/v1/docs#tag/Roles/operation/getRoles
### new-member
#### Prompt
Emit new event when a new member is added to a circle or assigned to a role. Requires circle ID as a prop.
#### URLs
- https://api.peerdom.org/v1/docs#tag/Peers/operation/getPeers
## Actions
### create-role
#### Prompt
Create a new role within a specified circle. Requires circle ID, role name, and optionally description or linked domains.
#### URLs
- https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole
### assign-member-to-role
#### Prompt
Assign a member to a role within a circle. Requires role ID and member ID.
#### URLs
- https://api.peerdom.org/v1/docs#tag/Roles/operation/assignRole
### update-role
#### Prompt
Update an existing role's attributes such as name, description, or linked domains. Requires role ID.
#### URLs
- https://api.peerdom.org/v1/docs#tag/Roles/operation/updateRoleById

Linked issue comments: user: login: sergio-eliot-rodriguez
created_at: 2025-04-23T12:36:42Z
body: Peerdom provided API Key, also 30 days free trial
author_association: COLLABORATOR
user: login: vunguyenhung
created_at: 2025-04-25T01:58:36Z
body: Hello everyone, I have tested this PR and there're some test cases failed or needed improvement. 

Please check the test report below for more information
https://vunguyenhung.notion.site/Components-peerdom-1dfbf548bb5e81c4ab54c039ea86c89c
author_association: CONTRIBUTOR
user: login: vunguyenhung
created_at: 2025-04-29T00:13:58Z
body: Hi everyone, all test cases are passed! Ready for release! 

Test report
https://vunguyenhung.notion.site/Components-peerdom-1dfbf548bb5e81c4ab54c039ea86c89c
author_association: CONTRIBUTOR

Diff:
diff --git a/components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs b/components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs
new file mode 100644
index 0000000000000..c90374a89697c
--- /dev/null
+++ b/components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs
@@ -0,0 +1,59 @@
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-assign-member-to-role",
+  name: "Assign Member to Role",
+  description: "Assigns a member to a role within a circle using the Peerdom API. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    roleId: {
+      propDefinition: [
+        peerdom,
+        "roleId",
+      ],
+    },
+    memberId: {
+      propDefinition: [
+        peerdom,
+        "memberId",
+      ],
+    },
+    percentage: {
+      type: "integer",
+      label: "Percentage",
+      description: "The percentage of the role assigned to the member",
+      optional: true,
+      min: 0,
+      max: 100,
+    },
+    focus: {
+      type: "string",
+      label: "Focus",
+      description: "The focus of the role assigned to the member",
+      optional: true,
+    },
+    electedUntil: {
+      type: "string",
+      label: "Elected Until",
+      description: "The date until which the member is elected to the role (YYYY-MM-DD)",
+      optional: true,
+    },
+  },
+  async run({ $ }) {
+    const response = await this.peerdom.assignMemberToRole({
+      $,
+      roleId: this.roleId,
+      data: {
+        peerId: this.memberId,
+        percentage: this.percentage,
+        focus: this.focus,
+        electedUntil: this.electedUntil,
+      },
+    });
+
+    $.export("$summary", `Successfully assigned member with ID ${this.memberId} to role with ID ${this.roleId}`);
+    return response;
+  },
+};
diff --git a/components/peerdom/actions/create-role/create-role.mjs b/components/peerdom/actions/create-role/create-role.mjs
new file mode 100644
index 0000000000000..52d8fb522aaa9
--- /dev/null
+++ b/components/peerdom/actions/create-role/create-role.mjs
@@ -0,0 +1,96 @@
+import { ConfigurationError } from "@pipedream/platform";
+import { parseObject } from "../../common/utils.mjs";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-create-role",
+  name: "Create Role",
+  description: "Create a new role within a specified circle. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    name: {
+      propDefinition: [
+        peerdom,
+        "name",
+      ],
+    },
+    mapId: {
+      propDefinition: [
+        peerdom,
+        "mapId",
+      ],
+    },
+    parentId: {
+      propDefinition: [
+        peerdom,
+        "groupId",
+      ],
+    },
+    electable: {
+      propDefinition: [
+        peerdom,
+        "electable",
+      ],
+      optional: true,
+    },
+    external: {
+      propDefinition: [
+        peerdom,
+        "external",
+      ],
+      optional: true,
+    },
+    color: {
+      propDefinition: [
+        peerdom,
+        "color",
+      ],
+      optional: true,
+    },
+    shape: {
+      propDefinition: [
+        peerdom,
+        "shape",
+      ],
+      optional: true,
+    },
+    customFields: {
+      propDefinition: [
+        peerdom,
+        "customFields",
+      ],
+      optional: true,
+    },
+    groupEmail: {
+      propDefinition: [
+        peerdom,
+        "groupEmail",
+      ],
+      optional: true,
+    },
+  },
+  async run({ $ }) {
+    try {
+      const {
+        peerdom,
+        customFields,
+        ...data
+      } = this;
+
+      const response = await peerdom.createRole({
+        $,
+        data: {
+          ...data,
+          customFields: parseObject(customFields),
+        },
+      });
+
+      $.export("$summary", `Successfully created role: ${response.name}`);
+      return response;
+    } catch ({ response }) {
+      throw new ConfigurationError(response.data.message);
+    }
+  },
+};
diff --git a/components/peerdom/actions/update-role/update-role.mjs b/components/peerdom/actions/update-role/update-role.mjs
new file mode 100644
index 0000000000000..0b26a17dab159
--- /dev/null
+++ b/components/peerdom/actions/update-role/update-role.mjs
@@ -0,0 +1,99 @@
+import { ConfigurationError } from "@pipedream/platform";
+import { parseObject } from "../../common/utils.mjs";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-update-role",
+  name: "Update Role",
+  description: "Update an existing role's attributes such as name, description, or linked domains. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    roleId: {
+      propDefinition: [
+        peerdom,
+        "roleId",
+      ],
+    },
+    name: {
+      propDefinition: [
+        peerdom,
+        "name",
+      ],
+      optional: true,
+    },
+    parentId: {
+      propDefinition: [
+        peerdom,
+        "groupId",
+      ],
+    },
+    electable: {
+      propDefinition: [
+        peerdom,
+        "electable",
+      ],
+      optional: true,
+    },
+    external: {
+      propDefinition: [
+        peerdom,
+        "external",
+      ],
+      optional: true,
+    },
+    color: {
+      propDefinition: [
+        peerdom,
+        "color",
+      ],
+      optional: true,
+    },
+    shape: {
+      propDefinition: [
+        peerdom,
+        "shape",
+      ],
+      optional: true,
+    },
+    customFields: {
+      propDefinition: [
+        peerdom,
+        "customFields",
+      ],
+      optional: true,
+    },
+    groupEmail: {
+      propDefinition: [
+        peerdom,
+        "groupEmail",
+      ],
+      optional: true,
+    },
+  },
+  async run({ $ }) {
+    try {
+      const {
+        peerdom,
+        roleId,
+        customFields,
+        ...data
+      } = this;
+
+      const response = await peerdom.updateRole({
+        $,
+        roleId,
+        data: {
+          ...data,
+          customFields: parseObject(customFields),
+        },
+      });
+
+      $.export("$summary", `Successfully updated role with ID ${this.roleId}`);
+      return response;
+    } catch ({ response }) {
+      throw new ConfigurationError(response.data.message);
+    }
+  },
+};
diff --git a/components/peerdom/common/constants.mjs b/components/peerdom/common/constants.mjs
new file mode 100644
index 0000000000000..fd3d1ee72fa1d
--- /dev/null
+++ b/components/peerdom/common/constants.mjs
@@ -0,0 +1,6 @@
+export const LIMIT = 100;
+
+export const SHAPE_OPTIONS = [
+  "circle",
+  "hexagon",
+];
diff --git a/components/peerdom/common/utils.mjs b/components/peerdom/common/utils.mjs
new file mode 100644
index 0000000000000..dcc9cc61f6f41
--- /dev/null
+++ b/components/peerdom/common/utils.mjs
@@ -0,0 +1,24 @@
+export const parseObject = (obj) => {
+  if (!obj) return undefined;
+
+  if (Array.isArray(obj)) {
+    return obj.map((item) => {
+      if (typeof item === "string") {
+        try {
+          return JSON.parse(item);
+        } catch (e) {
+          return item;
+        }
+      }
+      return item;
+    });
+  }
+  if (typeof obj === "string") {
+    try {
+      return JSON.parse(obj);
+    } catch (e) {
+      return obj;
+    }
+  }
+  return obj;
+};
diff --git a/components/peerdom/package.json b/components/peerdom/package.json
index 062fe5f18d560..dca78856fc590 100644
--- a/components/peerdom/package.json
+++ b/components/peerdom/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@pipedream/peerdom",
-  "version": "0.0.1",
+  "version": "0.1.0",
   "description": "Pipedream Peerdom Components",
   "main": "peerdom.app.mjs",
   "keywords": [
@@ -11,5 +11,8 @@
   "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
   "publishConfig": {
     "access": "public"
+  },
+  "dependencies": {
+    "@pipedream/platform": "^3.0.3"
   }
-}
\ No newline at end of file
+}
diff --git a/components/peerdom/peerdom.app.mjs b/components/peerdom/peerdom.app.mjs
index c38375fda2339..a638fda5a2569 100644
--- a/components/peerdom/peerdom.app.mjs
+++ b/components/peerdom/peerdom.app.mjs
@@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The ID should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set to `true` if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexadecimal string format, e.g. `#f3a935`",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node (group/role) communication",
+      optional: true,
+    },
+
+    circleId: {
+      type: "string",
+      label: "Circle ID",
+      description: "The ID of the circle (organization)",
+    },
+    roleName: {
+      type: "string",
+      label: "Role Name",
+      description: "The name of the role to be created",
+    },
+    description: {
+      type: "string",
+      label: "Description",
+      description: "Optional description for the role",
+      optional: true,
+    },
+    linkedDomains: {
+      type: "string[]",
+      label: "Linked Domains",
+      description: "Optional linked domains for the role",
+      optional: true,
+    },
+  },
   methods: {
-    // this.$auth contains connected account data
-    authKeys() {
-      console.log(Object.keys(this.$auth));
+    _baseUrl() {
+      return "https://api.peerdom.org/v1";
+    },
+    _headers() {
+      return {
+        "content-type": "application/json",
+        "x-api-key": `${this.$auth.api_key}`,
+      };
+    },
+    _makeRequest({
+      $ = this, path, ...opts
+    }) {
+      return axios($, {
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        ...opts,
+      });
+    },
+    listGroups(opts = {}) {
+      return this._makeRequest({
+        path: "/groups",
+        ...opts,
+      });
+    },
+    listMaps(opts = {}) {
+      return this._makeRequest({
+        path: "/maps",
+        ...opts,
+      });
+    },
+    listRoles(opts = {}) {
+      return this._makeRequest({
+        path: "/roles",
+        ...opts,
+      });
+    },
+    listPeers(opts = {}) {
+      return this._makeRequest({
+        path: "/peers",
+        ...opts,
+      });
+    },
+    createRole(opts = {}) {
+      return this._makeRequest({
+        method: "POST",
+        path: "/roles",
+        ...opts,
+      });
+    },
+    updateRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "PUT",
+        path: `/roles/${roleId}`,
+        ...opts,
+      });
+    },
+    async assignMemberToRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "POST",
+        path: `/roles/${roleId}/peers`,
+        ...opts,
+      });
+    },
+    async *paginate({
+      fn, params = {}, maxResults = null, ...opts
+    }) {
+      let hasMore = false;
+      let count = 0;
+      let page = 0;
+
+      do {
+        params.limit = LIMIT;
+        params.offset = (LIMIT * page++) + 1;
+        const data = await fn({
+          params,
+          ...opts,
+        });
+        for (const d of data) {
+          yield d;
+
+          if (maxResults && ++count === maxResults) {
+            return count;
+          }
+        }
+
+        hasMore = data.length;
+
+      } while (hasMore);
     },
   },
 };
diff --git a/components/peerdom/sources/common/base.mjs b/components/peerdom/sources/common/base.mjs
new file mode 100644
index 0000000000000..03ee88ac7525e
--- /dev/null
+++ b/components/peerdom/sources/common/base.mjs
@@ -0,0 +1,61 @@
+import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  props: {
+    peerdom,
+    db: "$.service.db",
+    timer: {
+      type: "$.interface.timer",
+      default: {
+        intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
+      },
+    },
+  },
+  methods: {
+    _getLastId() {
+      return this.db.get("lastId") || "";
+    },
+    _setLastId(lastId) {
+      this.db.set("lastId", lastId);
+    },
+    sortItems(items) {
+      return items;
+    },
+    async emitEvent(maxResults = false) {
+      const lastId = this._getLastId();
+      const fn = this.getFunction();
+
+      const response = this.sortItems(await fn());
+
+      let responseArray = [];
+      for (const item of response.reverse()) {
+        if (item.id == lastId) break;
+        responseArray.push(item);
+      }
+
+      if (responseArray.length) {
+        if (maxResults && (responseArray.length > maxResults)) {
+          responseArray.length = maxResults;
+        }
+        this._setLastId(responseArray[0].id);
+      }
+
+      for (const item of responseArray.reverse()) {
+        this.$emit(item, {
+          id: item.id,
+          summary: this.getSummary(item),
+          ts: Date.now(),
+        });
+      }
+    },
+  },
+  hooks: {
+    async deploy() {
+      await this.emitEvent(25);
+    },
+  },
+  async run() {
+    await this.emitEvent();
+  },
+};
diff --git a/components/peerdom/sources/new-member/new-member.mjs b/components/peerdom/sources/new-member/new-member.mjs
new file mode 100644
index 0000000000000..3a5d172f972b8
--- /dev/null
+++ b/components/peerdom/sources/new-member/new-member.mjs
@@ -0,0 +1,22 @@
+import common from "../common/base.mjs";
+import sampleEmit from "./test-event.mjs";
+
+export default {
+  ...common,
+  key: "peerdom-new-member",
+  name: "New Member Added",
+  description: "Emit new event when a new member is added to a group. [See the documentation](https://api.peerdom.org/v1/docs#tag/Peers/operation/getPeers)",
+  version: "0.0.1",
+  type: "source",
+  dedupe: "unique",
+  methods: {
+    ...common.methods,
+    getFunction() {
+      return this.peerdom.listPeers;
+    },
+    getSummary(item) {
+      return `New Member: ${item.firstName} ${item.lastName || ""}`;
+    },
+  },
+  sampleEmit,
+};
diff --git a/components/peerdom/sources/new-member/test-event.mjs b/components/peerdom/sources/new-member/test-event.mjs
new file mode 100644
index 0000000000000..0ad7248f09309
--- /dev/null
+++ b/components/peerdom/sources/new-member/test-event.mjs
@@ -0,0 +1,19 @@
+export default {
+  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
+  "firstName": "string",
+  "lastName": "string",
+  "nickName": "string",
+  "slug": "string",
+  "avatarUrl": "string",
+  "birthdate": "2019-08-24T14:15:22Z",
+  "contribution": 0,
+  "contributionUnit": "string",
+  "customFields": [
+    {
+      "name": "string",
+      "values": [
+        "string"
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/components/peerdom/sources/new-role/new-role.mjs b/components/peerdom/sources/new-role/new-role.mjs
new file mode 100644
index 0000000000000..5741b1141cb89
--- /dev/null
+++ b/components/peerdom/sources/new-role/new-role.mjs
@@ -0,0 +1,25 @@
+import common from "../common/base.mjs";
+import sampleEmit from "./test-event.mjs";
+
+export default {
+  ...common,
+  key: "peerdom-new-role",
+  name: "New Role Created",
+  description: "Emit new event when a new role is created in a circle. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "source",
+  dedupe: "unique",
+  methods: {
+    ...common.methods,
+    getFunction() {
+      return this.peerdom.listRoles;
+    },
+    getSummary(item) {
+      return `New Role Created: ${item.name}`;
+    },
+    sortItems(items) {
+      return items.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
+    },
+  },
+  sampleEmit,
+};
diff --git a/components/peerdom/sources/new-role/test-event.mjs b/components/peerdom/sources/new-role/test-event.mjs
new file mode 100644
index 0000000000000..86faf17132eeb
--- /dev/null
+++ b/components/peerdom/sources/new-role/test-event.mjs
@@ -0,0 +1,50 @@
+export default {
+  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
+  "name": "string",
+  "color": "string",
+  "createdAt": "2019-08-24T14:15:22Z",
+  "parentId": "70850378-7d3c-4f45-91b7-942d4dfbbd43",
+  "slug": "string",
+  "external": true,
+  "electable": true,
+  "salaryLevel": "string",
+  "mirrored": true,
+  "shape": "string",
+  "customFields": [
+    {
+      "name": "string",
+      "values": [
+        "string"
+      ]
+    }
+  ],
+  "holders": [
+    {
+      "peerId": "60a07d40-746d-414c-b70b-908ca16e7459",
+      "contribution": 0,
+      "contributionUnit": "string"
+    }
+  ],
+  "goals": [
+    {
+    "endedAt": "2019-05-17",
+    "startedAt": "2019-05-17",
+    "archived": true,
+    "data": {
+      "order": "string",
+      "name": "string",
+      "isComplete": true,
+      "subgoals": [
+        {
+          "name": "string",
+          "percentComplete": 0,
+          "isComplete": true,
+          "amountComplete": 0,
+          "targetAmount": 0
+        }
+      ]
+    }
+    }
+  ],
+  "groupEmail": "string"
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 17593dfe05b88..0d93145ab8f9d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9514,7 +9514,11 @@ importers:
         specifier: ^2.0.0
         version: 2.0.0
 
-  components/peerdom: {}
+  components/peerdom:
+    dependencies:
+      '@pipedream/platform':
+        specifier: ^3.0.3
+        version: 3.0.3
 
   components/pendo: {}
 


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 8**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>components/peerdom/common/constants.mjs (1)</summary><blockquote>

`1-1`: **Consider documenting the LIMIT constant purpose**

While the name suggests it's a pagination limit, a brief comment explaining its specific usage would improve maintainability.


```diff
-export const LIMIT = 100;
+// Maximum number of items to retrieve per API request
+export const LIMIT = 100;
```

</blockquote></details>
<details>
<summary>components/peerdom/common/utils.mjs (1)</summary><blockquote>

`1-24`: **Robust object parsing utility**

The `parseObject` utility provides comprehensive error handling for JSON parsing across different input types. This will help prevent runtime errors when dealing with potentially malformed JSON input.

One optimization suggestion:


```diff
 export const parseObject = (obj) => {
   if (!obj) return undefined;

   if (Array.isArray(obj)) {
     return obj.map((item) => {
       if (typeof item === "string") {
         try {
           return JSON.parse(item);
         } catch (e) {
           return item;
         }
       }
       return item;
     });
   }
   if (typeof obj === "string") {
     try {
       return JSON.parse(obj);
     } catch (e) {
       return obj;
     }
   }
   return obj;
 };
```

Consider adding a brief JSDoc comment explaining the function's purpose and usage to improve code documentation.

</blockquote></details>
<details>
<summary>components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs (1)</summary><blockquote>

`37-42`: **Add date format validation for electedUntil**

The `electedUntil` property accepts a string in YYYY-MM-DD format, but there's no validation to ensure users input a valid date.

Consider adding a pattern validation to ensure the correct format:

```diff
electedUntil: {
  type: "string",
  label: "Elected Until",
  description: "The date until which the member is elected to the role (YYYY-MM-DD)",
  optional: true,
+  pattern: "^\\d{4}-\\d{2}-\\d{2}$",
+  patternError: "Please enter a valid date in YYYY-MM-DD format",
},
```

</blockquote></details>
<details>
<summary>components/peerdom/sources/new-role/test-event.mjs (1)</summary><blockquote>

`1-50`: **Improve test event with more realistic sample data**

While the structure is comprehensive, using generic values like "string" makes the test data less realistic.

Consider using more realistic values to better simulate real-world data:

```diff
export default {
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
+ "name": "Product Manager",
- "color": "string",
+ "color": "#4287f5",
  "createdAt": "2019-08-24T14:15:22Z",
  "parentId": "70850378-7d3c-4f45-91b7-942d4dfbbd43",
- "slug": "string",
+ "slug": "product-manager",
  "external": true,
  "electable": true,
- "salaryLevel": "string",
+ "salaryLevel": "Level 3",
  "mirrored": true,
- "shape": "string",
+ "shape": "circle",
  "customFields": [
    {
-     "name": "string",
+     "name": "Department",
      "values": [
-       "string"
+       "Engineering"
      ]
    }
  ],
  // ... and so on for other fields
```

Also, note the inconsistent date formats between `createdAt` (ISO 8601) and dates in the goals array (YYYY-MM-DD). Ensure this matches the expected API behavior.

</blockquote></details>
<details>
<summary>components/peerdom/actions/create-role/create-role.mjs (1)</summary><blockquote>

`76-80`: **Restructure variable destructuring**

The code destructures `peerdom` from `this` but then uses the imported `peerdom` variable instead of the local one.

Either use the local variable or remove it from the destructuring:

```diff
    try {
      const {
-       peerdom,
        customFields,
        ...data
      } = this;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d285e5f3cd0e40594d66faeea44d726fc25f067b and dd5d498624d79c66a6aad7e3aba307a20d24499c.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (12)</summary>

* `components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs` (1 hunks)
* `components/peerdom/actions/create-role/create-role.mjs` (1 hunks)
* `components/peerdom/actions/update-role/update-role.mjs` (1 hunks)
* `components/peerdom/common/constants.mjs` (1 hunks)
* `components/peerdom/common/utils.mjs` (1 hunks)
* `components/peerdom/package.json` (2 hunks)
* `components/peerdom/peerdom.app.mjs` (1 hunks)
* `components/peerdom/sources/common/base.mjs` (1 hunks)
* `components/peerdom/sources/new-member/new-member.mjs` (1 hunks)
* `components/peerdom/sources/new-member/test-event.mjs` (1 hunks)
* `components/peerdom/sources/new-role/new-role.mjs` (1 hunks)
* `components/peerdom/sources/new-role/test-event.mjs` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>components/peerdom/sources/new-member/test-event.mjs (1)</summary>

`1-19`: **Test event structure looks good**

The test event contains all expected member properties including personal details, identification, and customizable fields. This structure aligns well with what would be expected from the Peerdom API for member entities.

</details>
<details>
<summary>components/peerdom/package.json (2)</summary>

`3-3`: **Version increment appropriate for new features**

The version change from 0.0.1 to 0.1.0 follows semantic versioning principles for adding new functionality without breaking changes.

---

`14-17`: **Dependencies correctly added**

The addition of the Pipedream platform dependency is appropriate for this integration. The caret versioning (^3.0.3) allows for compatible updates.

</details>
<details>
<summary>components/peerdom/common/constants.mjs (1)</summary>

`3-6`: **Shape options look appropriate**

The shape options ("circle" and "hexagon") are clearly defined and appear to align with Peerdom's visual representation of roles.

</details>
<details>
<summary>components/peerdom/sources/new-role/new-role.mjs (1)</summary>

`4-22`: **LGTM! The source component looks well-structured**

The implementation extends the common base component appropriately and provides the necessary specialized methods for handling role events.

</details>
<details>
<summary>components/peerdom/actions/create-role/create-role.mjs (1)</summary>

`25-30`: **Verify if `parentId` should use the `groupId` property definition**

The property is named `parentId` but uses the `groupId` property definition from the Peerdom app. This might be confusing if they represent different concepts.

Please confirm if `parentId` and `groupId` are interchangeable terms in the Peerdom API or if this should be updated for consistency.

</details>
<details>
<summary>components/peerdom/peerdom.app.mjs (1)</summary>

`207-214`: **Pagination offset starts at 1 – may skip the first record**

Most REST paginations are zero-based. Setting `offset = (LIMIT * page++) + 1` risks omitting the very first result. Recommend starting at `0` unless the API docs explicitly state otherwise.

```diff
-params.offset = (LIMIT * page++) + 1;
+params.offset = LIMIT * page++;
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T21:12:55Z
user: login: GTFalcao
body: I suggested a few description improvements. Moving it to QA
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T18:16:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/peerdom/peerdom.app.mjs (1)</summary><blockquote>

`151-174`: **API list methods don't extract data from response**

The methods `listGroups`, `listMaps`, `listRoles`, and `listPeers` return the entire Axios response object instead of just the response data. This will cause the option loaders to fail, as they expect an array of items to map through.


Apply this fix to extract only the data property in the API response:

```diff
 listGroups(opts = {}) {
   return this._makeRequest({
     path: "/groups",
     ...opts,
-  });
+  }).then(({ data }) => data);
 },
```

Apply the same change to the other list methods: `listMaps`, `listRoles`, and `listPeers`.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>components/peerdom/peerdom.app.mjs (1)</summary><blockquote>

`200-224`: **Improve pagination logic**

The `paginate` method has a potential issue with the `hasMore` logic. It currently checks `data.length` which would be true as long as there's at least one item returned, even if we've reached the last page.


A more reliable approach would be to check if the number of items returned is less than the requested limit:

```diff
-        hasMore = data.length;
+        hasMore = data.length === LIMIT;
```

This ensures pagination stops when the API returns fewer items than requested, indicating we've reached the last partial page.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between dd5d498624d79c66a6aad7e3aba307a20d24499c and 43e4295c34450acab5a498d90043ba53c3ece969.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/peerdom/peerdom.app.mjs` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Publish TypeScript components
* GitHub Check: Verify TypeScript components

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T19:00:43Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/peerdom/sources/common/base.mjs (1)</summary><blockquote>

`29-29`: _⚠️ Potential issue_

**API response handling issue - raw Axios response used incorrectly**

The code assumes that `fn()` returns an array of items, but based on past review comments, it appears that Peerdom app methods return full Axios responses, not just `response.data`. This will cause the `sortItems` function to operate on the wrong type.



Apply this fix:
```diff
-const response = this.sortItems(await fn());
+const { data } = await fn();
+const response = this.sortItems(data);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>components/peerdom/sources/common/base.mjs (5)</summary><blockquote>

`16-21`: **Consider adding type documentation for the ID storage**

The database methods handle an ID value but don't document what type of ID is expected (string, number, etc.), which could lead to confusion for developers extending this base.


```diff
    _getLastId() {
+     // Returns the string ID of the last processed item or empty string if none exists
      return this.db.get("lastId") || "";
    },
    _setLastId(lastId) {
+     // Stores the string ID of the most recently processed item
      this.db.set("lastId", lastId);
    },
```

---

`32-35`: **Add validation for item structure**

The code assumes each item has an `id` property without validating it, which could lead to errors if the API response format changes.


```diff
    for (const item of response.reverse()) {
+     if (!item || typeof item.id === 'undefined') {
+       console.warn('Received item without valid id, skipping:', item);
+       continue;
+     }
      if (item.id == lastId) break;
      responseArray.push(item);
    }
```

---

`22-24`: **Provide more meaningful default sorting implementation**

The current default implementation of `sortItems` doesn't actually sort and might confuse developers. Consider adding documentation or implementing a more useful default.


```diff
+   /**
+    * Override this method to customize sorting of items from the API
+    * By default, returns the items in their original order
+    * @param {Array} items - The items to sort
+    * @returns {Array} The sorted items
+    */
    sortItems(items) {
      return items;
    },
```

---

`10-13`: **Allow customization of polling interval**

The source uses a default polling interval, but users might want to customize it. Consider making it configurable through props.


```diff
    timer: {
      type: "$.interface.timer",
+     label: "Polling Interval",
+     description: "How often to poll the Peerdom API for new data",
      default: {
        intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
      },
+     viewOptions: {
+       min: 60,
+       step: 60,
+     },
    },
```

---

`38-40`: **Consider using slice for clarity**

The current code modifies the array length directly to limit results, but using `slice()` would be more idiomatic and clear.


```diff
    if (responseArray.length) {
      if (maxResults && (responseArray.length > maxResults)) {
-       responseArray.length = maxResults;
+       responseArray = responseArray.slice(0, maxResults);
      }
      this._setLastId(responseArray[0].id);
    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 43e4295c34450acab5a498d90043ba53c3ece969 and 9021d2bd6335be0b4d075be1bff018b048e571b1.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `components/peerdom/sources/common/base.mjs` (1 hunks)
* `components/peerdom/sources/new-member/new-member.mjs` (1 hunks)
* `components/peerdom/sources/new-role/new-role.mjs` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* components/peerdom/sources/new-role/new-role.mjs
* components/peerdom/sources/new-member/new-member.mjs

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Verify TypeScript components
* GitHub Check: pnpm publish
* GitHub Check: Publish TypeScript components
* GitHub Check: Lint Code Base

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T20:30:06Z
user: login: GTFalcao
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-30T13:31:12Z

Review comments:
diff_hunk: @@ -0,0 +1,59 @@
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-assign-member-to-role",
+  name: "Assign Member to Role",
+  description: "Assigns a member to a role within a circle using the Peerdom API. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    roleId: {
+      propDefinition: [
+        peerdom,
+        "roleId",
+      ],
+    },
+    memberId: {
+      propDefinition: [
+        peerdom,
+        "memberId",
+      ],
+    },
+    percentage: {
+      type: "integer",
+      label: "Percentage",
+      description: "The percentage of the role assigned to the member",
+      optional: true,
+      min: 0,
+      max: 100,
+    },
+    focus: {
+      type: "string",
+      label: "Focus",
+      description: "The focus of the role assigned to the member",
+      optional: true,
+    },
+    electedUntil: {
+      type: "string",
+      label: "Elected Until",
+      description: "The date until which the member is elected to the role (YYYY-MM-DD)",
+      optional: true,
+    },
+  },
+  async run({ $ }) {
+    const response = await this.peerdom.assignMemberToRole({
+      $,
+      roleId: this.roleId,
+      data: {
+        peerId: this.memberId,
+        percentage: this.percentage,
+        focus: this.focus,
+        electedUntil: this.electedUntil,
+      },
+    });
+
+    $.export("$summary", `Successfully assigned member with ID ${this.memberId} to role with ID ${this.roleId}`);
+    return response;
+  },
path: components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling to the run method**

The current implementation doesn't handle API errors. If the `assignMemberToRole` call fails, it will throw an uncaught exception.

Add a try/catch block similar to what's in the create-role action:

```diff
async run({ $ }) {
+  try {
    const response = await this.peerdom.assignMemberToRole({
      $,
      roleId: this.roleId,
      data: {
        peerId: this.memberId,
        percentage: this.percentage,
        focus: this.focus,
        electedUntil: this.electedUntil,
      },
    });

    $.export("$summary", `Successfully assigned member with ID ${this.memberId} to role with ID ${this.roleId}`);
    return response;
+  } catch ({ response }) {
+    throw new ConfigurationError(response.data.message);
+  }
},
```

Don't forget to add the import at the top:

```diff
import peerdom from "../../peerdom.app.mjs";
+import { ConfigurationError } from "@pipedream/platform";
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// at the top of components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs
import peerdom from "../../peerdom.app.mjs";
import { ConfigurationError } from "@pipedream/platform";

  async run({ $ }) {
    try {
      const response = await this.peerdom.assignMemberToRole({
        $,
        roleId: this.roleId,
        data: {
          peerId: this.memberId,
          percentage: this.percentage,
          focus: this.focus,
          electedUntil: this.electedUntil,
        },
      });

      $.export(
        "$summary",
        `Successfully assigned member with ID ${this.memberId} to role with ID ${this.roleId}`
      );
      return response;
    } catch ({ response }) {
      throw new ConfigurationError(response.data.message);
    }
  },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,96 @@
+import { ConfigurationError } from "@pipedream/platform";
+import { parseObject } from "../../common/utils.mjs";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-create-role",
+  name: "Create Role",
+  description: "Create a new role within a specified circle. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    name: {
+      propDefinition: [
+        peerdom,
+        "name",
+      ],
+    },
+    mapId: {
+      propDefinition: [
+        peerdom,
+        "mapId",
+      ],
+    },
+    parentId: {
+      propDefinition: [
+        peerdom,
+        "groupId",
+      ],
+    },
+    electable: {
+      propDefinition: [
+        peerdom,
+        "electable",
+      ],
+      optional: true,
+    },
+    external: {
+      propDefinition: [
+        peerdom,
+        "external",
+      ],
+      optional: true,
+    },
+    color: {
+      propDefinition: [
+        peerdom,
+        "color",
+      ],
+      optional: true,
+    },
+    shape: {
+      propDefinition: [
+        peerdom,
+        "shape",
+      ],
+      optional: true,
+    },
+    customFields: {
+      propDefinition: [
+        peerdom,
+        "customFields",
+      ],
+      optional: true,
+    },
+    groupEmail: {
+      propDefinition: [
+        peerdom,
+        "groupEmail",
+      ],
+      optional: true,
+    },
+  },
+  async run({ $ }) {
+    try {
+      const {
+        peerdom,
+        customFields,
+        ...data
+      } = this;
+
+      const response = await peerdom.createRole({
+        $,
+        data: {
+          ...data,
+          customFields: parseObject(customFields),
+        },
+      });
+
+      $.export("$summary", `Successfully created role: ${response.name}`);
+      return response;
+    } catch ({ response }) {
+      throw new ConfigurationError(response.data.message);
+    }
+  },
path: components/peerdom/actions/create-role/create-role.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve error handling for edge cases**

The current error handling assumes all errors will have a `response.data.message` property, which may not be true for network errors or other exceptional cases.

Add a fallback error message for cases where the expected error structure isn't available:

```diff
  async run({ $ }) {
    try {
      const {
        peerdom,
        customFields,
        ...data
      } = this;

      const response = await peerdom.createRole({
        $,
        data: {
          ...data,
          customFields: parseObject(customFields),
        },
      });

      $.export("$summary", `Successfully created role: ${response.name}`);
      return response;
-    } catch ({ response }) {
-      throw new ConfigurationError(response.data.message);
+    } catch (error) {
+      const errorMessage = error.response?.data?.message || "An error occurred while creating the role";
+      throw new ConfigurationError(errorMessage);
    }
  },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  async run({ $ }) {
    try {
      const {
        peerdom,
        customFields,
        ...data
      } = this;

      const response = await peerdom.createRole({
        $,
        data: {
          ...data,
          customFields: parseObject(customFields),
        },
      });

      $.export("$summary", `Successfully created role: ${response.name}`);
      return response;
    } catch (error) {
      const errorMessage =
        error.response?.data?.message ||
        "An error occurred while creating the role";
      throw new ConfigurationError(errorMessage);
    }
  },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,99 @@
+import { ConfigurationError } from "@pipedream/platform";
+import { parseObject } from "../../common/utils.mjs";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-update-role",
+  name: "Update Role",
+  description: "Update an existing role's attributes such as name, description, or linked domains. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    roleId: {
+      propDefinition: [
+        peerdom,
+        "roleId",
+      ],
+    },
+    name: {
+      propDefinition: [
+        peerdom,
+        "name",
+      ],
+      optional: true,
+    },
path: components/peerdom/actions/update-role/update-role.mjs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Prop `name` conflicts with the component’s own `name` field**

Inside the `run()` method you later spread all remaining properties from `this` into `data`.  
Because both the component and the prop are called `name`, the string `"Update Role"` (component label) will overwrite the user-supplied role name, and will also be sent to the API whenever the user does *not* provide a `name` prop.

Either rename the prop (e.g. `roleName`) or build the request payload explicitly instead of relying on `...data`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,99 @@
+import { ConfigurationError } from "@pipedream/platform";
+import { parseObject } from "../../common/utils.mjs";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-update-role",
+  name: "Update Role",
+  description: "Update an existing role's attributes such as name, description, or linked domains. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    roleId: {
+      propDefinition: [
+        peerdom,
+        "roleId",
+      ],
+    },
+    name: {
+      propDefinition: [
+        peerdom,
+        "name",
+      ],
+      optional: true,
+    },
+    parentId: {
+      propDefinition: [
+        peerdom,
+        "groupId",
+      ],
+    },
path: components/peerdom/actions/update-role/update-role.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`parentId` should be optional when updating a role**

Updating a subset of attributes should not force callers to provide a `parentId`.  
Add `optional: true` to avoid unnecessary validation errors.

```diff
 parentId: {
   propDefinition: [
     peerdom,
     "groupId",
   ],
+  optional: true,
 },
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    parentId: {
      propDefinition: [
        peerdom,
        "groupId",
      ],
      optional: true,
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,99 @@
+import { ConfigurationError } from "@pipedream/platform";
+import { parseObject } from "../../common/utils.mjs";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  key: "peerdom-update-role",
+  name: "Update Role",
+  description: "Update an existing role's attributes such as name, description, or linked domains. [See the documentation](https://api.peerdom.org/v1/docs)",
+  version: "0.0.1",
+  type: "action",
+  props: {
+    peerdom,
+    roleId: {
+      propDefinition: [
+        peerdom,
+        "roleId",
+      ],
+    },
+    name: {
+      propDefinition: [
+        peerdom,
+        "name",
+      ],
+      optional: true,
+    },
+    parentId: {
+      propDefinition: [
+        peerdom,
+        "groupId",
+      ],
+    },
+    electable: {
+      propDefinition: [
+        peerdom,
+        "electable",
+      ],
+      optional: true,
+    },
+    external: {
+      propDefinition: [
+        peerdom,
+        "external",
+      ],
+      optional: true,
+    },
+    color: {
+      propDefinition: [
+        peerdom,
+        "color",
+      ],
+      optional: true,
+    },
+    shape: {
+      propDefinition: [
+        peerdom,
+        "shape",
+      ],
+      optional: true,
+    },
+    customFields: {
+      propDefinition: [
+        peerdom,
+        "customFields",
+      ],
+      optional: true,
+    },
+    groupEmail: {
+      propDefinition: [
+        peerdom,
+        "groupEmail",
+      ],
+      optional: true,
+    },
+  },
+  async run({ $ }) {
+    try {
+      const {
+        peerdom,
+        roleId,
+        customFields,
+        ...data
+      } = this;
+
+      const response = await peerdom.updateRole({
+        $,
+        roleId,
+        data: {
+          ...data,
+          customFields: parseObject(customFields),
+        },
+      });
+
+      $.export("$summary", `Successfully updated role with ID ${this.roleId}`);
+      return response;
+    } catch ({ response }) {
+      throw new ConfigurationError(response.data.message);
+    }
+  },
path: components/peerdom/actions/update-role/update-role.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Several robustness issues in `run()`**

1. The spread operator pulls *all* fields from `this`, including non–prop metadata (`key`, `description`, etc.). These end up in the request body.
2. `parseObject(customFields)` is invoked even when `customFields` is `undefined`, which may throw if the util doesn’t guard against it.
3. The `catch` block destructures `response` from the error object.  Axios will provide `error.response`, but network or parsing errors won’t – leading to `Cannot destructure property 'data' of 'response' as it is undefined`.

Consider a safer implementation:

```diff
-const { peerdom, roleId, customFields, ...data } = this;
+const {
+  peerdom,
+  roleId,
+  name,
+  parentId,
+  electable,
+  external,
+  color,
+  shape,
+  customFields,
+  groupEmail,
+} = this;

+const payload = {
+  ...(name !== undefined && { name }),
+  ...(parentId !== undefined && { parentId }),
+  ...(electable !== undefined && { electable }),
+  ...(external !== undefined && { external }),
+  ...(color !== undefined && { color }),
+  ...(shape !== undefined && { shape }),
+  ...(groupEmail !== undefined && { groupEmail }),
+  ...(customFields !== undefined && { customFields: parseObject(customFields) }),
+};

 const response = await peerdom.updateRole({
   $,
   roleId,
-  data: {
-    ...data,
-    customFields: parseObject(customFields),
-  },
+  data: payload,
 });
 ...
 } catch (err) {
-  throw new ConfigurationError(response.data.message);
+  const msg = err?.response?.data?.message ?? err.message ?? "Unknown error";
+  throw new ConfigurationError(msg);
 }
```



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import common from "../common/base.mjs";
+import sampleEmit from "./test-event.mjs";
+
+export default {
+  ...common,
+  key: "peerdom-new-member",
+  name: "New Member Added",
+  description: "Emit new event when a new member is added to a group. [See the documentation](https://api.peerdom.org/v1/docs#tag/Peers/operation/getPeers)",
+  version: "0.0.1",
+  type: "source",
+  dedupe: "unique",
+  methods: {
+    ...common.methods,
+    getFunction() {
+      return this.peerdom.listPeers;
+    },
path: components/peerdom/sources/new-member/new-member.mjs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Method reference loses its binding – API calls will fail**

`getFunction()` returns an un-bound method (`this.peerdom.listPeers`).  
When `emitEvent` later calls `fn()`, `this` inside `listPeers` is `undefined`, causing `_makeRequest` to throw.

Bind the method:

```diff
 getFunction() {
-  return this.peerdom.listPeers;
+  return this.peerdom.listPeers.bind(this.peerdom);
 },
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    getFunction() {
      return this.peerdom.listPeers.bind(this.peerdom);
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  props: {
+    peerdom,
+    db: "$.service.db",
+    timer: {
+      type: "$.interface.timer",
+      default: {
+        intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
+      },
+    },
+  },
+  methods: {
+    _getLastId() {
+      return this.db.get("lastId") || "";
+    },
+    _setLastId(lastId) {
+      this.db.set("lastId", lastId);
+    },
+    async emitEvent(maxResults = false) {
+      const lastId = this._getLastId();
+      const fn = this.getFunction();
+
+      const response = await fn();
+
+      let responseArray = [];
+      for (const item of response) {
+        if (item.id == lastId) break;
+        responseArray.push(item);
+      }
+
path: components/peerdom/sources/common/base.mjs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`emitEvent` assumes the API wrapper returns an array**

`listPeers()` and friends currently return the full Axios response, not `response.data`, so
`for (const item of response)` will iterate over object keys instead of items, emitting garbage.

Either:

1. Change the app methods to return `response.data`, or  
2. Access `response.data` here.

```diff
-const response = await fn();
+const { data: response } = await fn();
```

Without this fix the source will emit nothing useful.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      const fn = this.getFunction();

      const { data: response } = await fn();

      let responseArray = [];
      for (const item of response) {
        if (item.id == lastId) break;
        responseArray.push(item);
      }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The Id should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set external to true if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexa decimal string format",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node(group/role) communication",
+      optional: true,
+    },
+
+    circleId: {
+      type: "string",
+      label: "Circle ID",
+      description: "The ID of the circle (organization)",
+    },
+    roleName: {
+      type: "string",
+      label: "Role Name",
+      description: "The name of the role to be created",
+    },
+    description: {
+      type: "string",
+      label: "Description",
+      description: "Optional description for the role",
+      optional: true,
+    },
+    linkedDomains: {
+      type: "string[]",
+      label: "Linked Domains",
+      description: "Optional linked domains for the role",
+      optional: true,
+    },
+  },
   methods: {
-    // this.$auth contains connected account data
-    authKeys() {
-      console.log(Object.keys(this.$auth));
+    _baseUrl() {
+      return "https://api.peerdom.org/v1";
+    },
+    _headers() {
+      return {
+        "content-type": "application/json",
+        "x-api-key": `${this.$auth.api_key}`,
+      };
+    },
+    _makeRequest({
+      $ = this, path, ...opts
+    }) {
+      return axios($, {
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        ...opts,
+      });
+    },
+    listGroups(opts = {}) {
+      return this._makeRequest({
+        path: "/groups",
+        ...opts,
+      });
+    },
+    listMaps(opts = {}) {
+      return this._makeRequest({
+        path: "/maps",
+        ...opts,
+      });
+    },
+    listRoles(opts = {}) {
+      return this._makeRequest({
+        path: "/roles",
+        ...opts,
+      });
+    },
+    listPeers(opts = {}) {
+      return this._makeRequest({
+        path: "/peers",
+        ...opts,
+      });
+    },
path: components/peerdom/peerdom.app.mjs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Data loaders return the whole Axios response instead of the payload**

`listGroups`, `listMaps`, `listRoles`, and `listPeers` return the raw response object, but the option loaders expect an array.

Add `.then(r => r.data)` **or** extract `data` inside each method:

```diff
 listGroups(opts = {}) {
-  return this._makeRequest({
+  return this._makeRequest({
     path: "/groups",
     ...opts,
-  });
+  }).then(({ data }) => data);
 },
```

Apply the same change to the other list* methods.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    listGroups(opts = {}) {
      return this._makeRequest({
        path: "/groups",
        ...opts,
      }).then(({ data }) => data);
    },
    listMaps(opts = {}) {
      return this._makeRequest({
        path: "/maps",
        ...opts,
      }).then(({ data }) => data);
    },
    listRoles(opts = {}) {
      return this._makeRequest({
        path: "/roles",
        ...opts,
      }).then(({ data }) => data);
    },
    listPeers(opts = {}) {
      return this._makeRequest({
        path: "/peers",
        ...opts,
      }).then(({ data }) => data);
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T21:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The Id should be a valid group ID.",
path: components/peerdom/peerdom.app.mjs
user: login: GTFalcao
body: ```suggestion
      description: "The ID should be a valid group ID.",
```
created_at: 2025-04-24T18:14:48Z
author_association: COLLABORATOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The Id should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set external to true if node is outside of the organization",
path: components/peerdom/peerdom.app.mjs
user: login: GTFalcao
body: ```suggestion
      description: "Set to `true` if node is outside of the organization",
```
created_at: 2025-04-24T18:15:15Z
author_association: COLLABORATOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The Id should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set external to true if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexa decimal string format",
path: components/peerdom/peerdom.app.mjs
user: login: GTFalcao
body: ```suggestion
      description: "The choice of color for the node in hexadecimal string format, e.g. `#f3a935`",
```
created_at: 2025-04-24T18:15:50Z
author_association: COLLABORATOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The Id should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set external to true if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexa decimal string format",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node(group/role) communication",
path: components/peerdom/peerdom.app.mjs
user: login: GTFalcao
body: ```suggestion
      description: "Email for node (group/role) communication",
```
created_at: 2025-04-24T18:16:12Z
author_association: COLLABORATOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The ID should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set to `true` if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexadecimal string format, e.g. `#f3a935`",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node (group/role) communication",
+      optional: true,
+    },
+
+    circleId: {
+      type: "string",
+      label: "Circle ID",
+      description: "The ID of the circle (organization)",
+    },
+    roleName: {
+      type: "string",
+      label: "Role Name",
+      description: "The name of the role to be created",
+    },
+    description: {
+      type: "string",
+      label: "Description",
+      description: "Optional description for the role",
+      optional: true,
+    },
+    linkedDomains: {
+      type: "string[]",
+      label: "Linked Domains",
+      description: "Optional linked domains for the role",
+      optional: true,
+    },
+  },
   methods: {
-    // this.$auth contains connected account data
-    authKeys() {
-      console.log(Object.keys(this.$auth));
+    _baseUrl() {
+      return "https://api.peerdom.org/v1";
+    },
+    _headers() {
+      return {
+        "content-type": "application/json",
+        "x-api-key": `${this.$auth.api_key}`,
+      };
+    },
+    _makeRequest({
+      $ = this, path, ...opts
+    }) {
+      return axios($, {
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        ...opts,
+      });
+    },
+    listGroups(opts = {}) {
+      return this._makeRequest({
+        path: "/groups",
+        ...opts,
+      });
+    },
+    listMaps(opts = {}) {
+      return this._makeRequest({
+        path: "/maps",
+        ...opts,
+      });
+    },
+    listRoles(opts = {}) {
+      return this._makeRequest({
+        path: "/roles",
+        ...opts,
+      });
+    },
+    listPeers(opts = {}) {
+      return this._makeRequest({
+        path: "/peers",
+        ...opts,
+      });
+    },
+    createRole(opts = {}) {
+      return this._makeRequest({
+        method: "POST",
+        path: "/roles",
+        ...opts,
+      });
+    },
+    updateRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "PUT",
+        path: `/roles/${roleId}`,
+        ...opts,
+      });
+    },
path: components/peerdom/peerdom.app.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Missing data extraction in updateRole method**

The `updateRole` method should also extract and return just the data property from the response.


```diff
 updateRole({
   roleId, ...opts
 }) {
   return this._makeRequest({
     method: "PUT",
     path: `/roles/${roleId}`,
     ...opts,
-  });
+  }).then(({ data }) => data);
 },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    updateRole({
      roleId, ...opts
    }) {
      return this._makeRequest({
        method: "PUT",
        path: `/roles/${roleId}`,
        ...opts,
      }).then(({ data }) => data);
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T19:00:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The ID should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set to `true` if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexadecimal string format, e.g. `#f3a935`",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node (group/role) communication",
+      optional: true,
+    },
+
+    circleId: {
+      type: "string",
+      label: "Circle ID",
+      description: "The ID of the circle (organization)",
+    },
+    roleName: {
+      type: "string",
+      label: "Role Name",
+      description: "The name of the role to be created",
+    },
+    description: {
+      type: "string",
+      label: "Description",
+      description: "Optional description for the role",
+      optional: true,
+    },
+    linkedDomains: {
+      type: "string[]",
+      label: "Linked Domains",
+      description: "Optional linked domains for the role",
+      optional: true,
+    },
+  },
   methods: {
-    // this.$auth contains connected account data
-    authKeys() {
-      console.log(Object.keys(this.$auth));
+    _baseUrl() {
+      return "https://api.peerdom.org/v1";
+    },
+    _headers() {
+      return {
+        "content-type": "application/json",
+        "x-api-key": `${this.$auth.api_key}`,
+      };
+    },
+    _makeRequest({
+      $ = this, path, ...opts
+    }) {
+      return axios($, {
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        ...opts,
+      });
+    },
+    listGroups(opts = {}) {
+      return this._makeRequest({
+        path: "/groups",
+        ...opts,
+      });
+    },
+    listMaps(opts = {}) {
+      return this._makeRequest({
+        path: "/maps",
+        ...opts,
+      });
+    },
+    listRoles(opts = {}) {
+      return this._makeRequest({
+        path: "/roles",
+        ...opts,
+      });
+    },
+    listPeers(opts = {}) {
+      return this._makeRequest({
+        path: "/peers",
+        ...opts,
+      });
+    },
+    createRole(opts = {}) {
+      return this._makeRequest({
+        method: "POST",
+        path: "/roles",
+        ...opts,
+      });
+    },
+    updateRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "PUT",
+        path: `/roles/${roleId}`,
+        ...opts,
+      });
+    },
+    async assignMemberToRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "POST",
+        path: `/roles/${roleId}/peers`,
+        ...opts,
+      });
+    },
+    async *paginate({
+      fn, params = {}, maxResults = null, ...opts
+    }) {
+      let hasMore = false;
+      let count = 0;
+      let page = 0;
+
+      do {
+        params.limit = LIMIT;
+        params.offset = (LIMIT * page++) + 1;
path: components/peerdom/peerdom.app.mjs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Off-by-one error in pagination offset calculation**

There's an off-by-one error in the offset calculation. When using zero-based pagination (which is common), the offset for the first page should be 0, second page should be LIMIT, etc.


```diff
-        params.offset = (LIMIT * page++) + 1;
+        params.offset = LIMIT * page++;
```

If the Peerdom API uses 1-based indexing for its offset, disregard this comment. But most APIs use 0-based indexing for pagination offsets.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        params.offset = LIMIT * page++;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T19:00:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The ID should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set to `true` if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexadecimal string format, e.g. `#f3a935`",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node (group/role) communication",
+      optional: true,
+    },
+
+    circleId: {
+      type: "string",
+      label: "Circle ID",
+      description: "The ID of the circle (organization)",
+    },
+    roleName: {
+      type: "string",
+      label: "Role Name",
+      description: "The name of the role to be created",
+    },
+    description: {
+      type: "string",
+      label: "Description",
+      description: "Optional description for the role",
+      optional: true,
+    },
+    linkedDomains: {
+      type: "string[]",
+      label: "Linked Domains",
+      description: "Optional linked domains for the role",
+      optional: true,
+    },
+  },
   methods: {
-    // this.$auth contains connected account data
-    authKeys() {
-      console.log(Object.keys(this.$auth));
+    _baseUrl() {
+      return "https://api.peerdom.org/v1";
+    },
+    _headers() {
+      return {
+        "content-type": "application/json",
+        "x-api-key": `${this.$auth.api_key}`,
+      };
+    },
+    _makeRequest({
+      $ = this, path, ...opts
+    }) {
+      return axios($, {
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        ...opts,
+      });
+    },
+    listGroups(opts = {}) {
+      return this._makeRequest({
+        path: "/groups",
+        ...opts,
+      });
+    },
+    listMaps(opts = {}) {
+      return this._makeRequest({
+        path: "/maps",
+        ...opts,
+      });
+    },
+    listRoles(opts = {}) {
+      return this._makeRequest({
+        path: "/roles",
+        ...opts,
+      });
+    },
+    listPeers(opts = {}) {
+      return this._makeRequest({
+        path: "/peers",
+        ...opts,
+      });
+    },
+    createRole(opts = {}) {
+      return this._makeRequest({
+        method: "POST",
+        path: "/roles",
+        ...opts,
+      });
+    },
path: components/peerdom/peerdom.app.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Missing data extraction in createRole method**

Similar to the list methods, the `createRole` method should also extract and return just the data from the API response to maintain consistency.


```diff
 createRole(opts = {}) {
   return this._makeRequest({
     method: "POST",
     path: "/roles",
     ...opts,
-  });
+  }).then(({ data }) => data);
 },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    createRole(opts = {}) {
      return this._makeRequest({
        method: "POST",
        path: "/roles",
        ...opts,
      }).then(({ data }) => data);
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T19:00:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,11 +1,227 @@
+import { axios } from "@pipedream/platform";
+import {
+  LIMIT, SHAPE_OPTIONS,
+} from "./common/constants.mjs";
+
 export default {
   type: "app",
   app: "peerdom",
-  propDefinitions: {},
+  propDefinitions: {
+    groupId: {
+      type: "string",
+      label: "Parent ID",
+      description: "The ID should be a valid group ID.",
+      async options() {
+        const data = await this.listGroups();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    mapId: {
+      type: "string",
+      label: "Map ID",
+      description: "The ID of the map",
+      async options() {
+        const data = await this.listMaps();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    roleId: {
+      type: "string",
+      label: "Role ID",
+      description: "ID of role to update.",
+      async options() {
+        const data = await this.listRoles();
+
+        return data.map(({
+          id: value, name: label,
+        }) => ({
+          label,
+          value,
+        }));
+      },
+    },
+    memberId: {
+      type: "string",
+      label: "Member ID",
+      description: "The ID of the member",
+      async options() {
+        const data = await this.listPeers();
+
+        return data.map(({
+          id: value, firstName, lastName,
+        }) => ({
+          label: `${firstName} ${lastName}`,
+          value,
+        }));
+      },
+    },
+    name: {
+      type: "string",
+      label: "Name",
+      description: "The name of the role to be created",
+    },
+    electable: {
+      type: "boolean",
+      label: "Electable",
+      description: "Whether the role is electable or not",
+    },
+    external: {
+      type: "boolean",
+      label: "External",
+      description: "Set to `true` if node is outside of the organization",
+    },
+    color: {
+      type: "string",
+      label: "Color",
+      description: "The choice of color for the node in hexadecimal string format, e.g. `#f3a935`",
+    },
+    shape: {
+      type: "string",
+      label: "Shape",
+      description: "Specifies the shape of the node that determines the visual representation of the node within the interface",
+      options: SHAPE_OPTIONS,
+      optional: true,
+    },
+    customFields: {
+      type: "object",
+      label: "Custom Fields",
+      description: "The custom fields for a group/role. You can add the properties from the predefined custom fields. [See the documentation](https://api.peerdom.org/v1/docs#tag/Roles/operation/postRole) for further details.",
+    },
+    groupEmail: {
+      type: "string",
+      label: "Group Email",
+      description: "Email for node (group/role) communication",
+      optional: true,
+    },
+
+    circleId: {
+      type: "string",
+      label: "Circle ID",
+      description: "The ID of the circle (organization)",
+    },
+    roleName: {
+      type: "string",
+      label: "Role Name",
+      description: "The name of the role to be created",
+    },
+    description: {
+      type: "string",
+      label: "Description",
+      description: "Optional description for the role",
+      optional: true,
+    },
+    linkedDomains: {
+      type: "string[]",
+      label: "Linked Domains",
+      description: "Optional linked domains for the role",
+      optional: true,
+    },
+  },
   methods: {
-    // this.$auth contains connected account data
-    authKeys() {
-      console.log(Object.keys(this.$auth));
+    _baseUrl() {
+      return "https://api.peerdom.org/v1";
+    },
+    _headers() {
+      return {
+        "content-type": "application/json",
+        "x-api-key": `${this.$auth.api_key}`,
+      };
+    },
+    _makeRequest({
+      $ = this, path, ...opts
+    }) {
+      return axios($, {
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        ...opts,
+      });
+    },
+    listGroups(opts = {}) {
+      return this._makeRequest({
+        path: "/groups",
+        ...opts,
+      });
+    },
+    listMaps(opts = {}) {
+      return this._makeRequest({
+        path: "/maps",
+        ...opts,
+      });
+    },
+    listRoles(opts = {}) {
+      return this._makeRequest({
+        path: "/roles",
+        ...opts,
+      });
+    },
+    listPeers(opts = {}) {
+      return this._makeRequest({
+        path: "/peers",
+        ...opts,
+      });
+    },
+    createRole(opts = {}) {
+      return this._makeRequest({
+        method: "POST",
+        path: "/roles",
+        ...opts,
+      });
+    },
+    updateRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "PUT",
+        path: `/roles/${roleId}`,
+        ...opts,
+      });
+    },
+    async assignMemberToRole({
+      roleId, ...opts
+    }) {
+      return this._makeRequest({
+        method: "POST",
+        path: `/roles/${roleId}/peers`,
+        ...opts,
+      });
+    },
path: components/peerdom/peerdom.app.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Unnecessary async keyword and missing data extraction**

The `assignMemberToRole` method is marked as `async` but doesn't use `await` inside. Also, it needs to extract the data from the response.


```diff
-async assignMemberToRole({
+assignMemberToRole({
   roleId, ...opts
 }) {
   return this._makeRequest({
     method: "POST",
     path: `/roles/${roleId}/peers`,
     ...opts,
-  });
+  }).then(({ data }) => data);
 },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    assignMemberToRole({
      roleId, ...opts
    }) {
      return this._makeRequest({
        method: "POST",
        path: `/roles/${roleId}/peers`,
        ...opts,
      }).then(({ data }) => data);
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T19:00:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
+import peerdom from "../../peerdom.app.mjs";
+
+export default {
+  props: {
+    peerdom,
+    db: "$.service.db",
+    timer: {
+      type: "$.interface.timer",
+      default: {
+        intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
+      },
+    },
+  },
+  methods: {
+    _getLastId() {
+      return this.db.get("lastId") || "";
+    },
+    _setLastId(lastId) {
+      this.db.set("lastId", lastId);
+    },
+    sortItems(items) {
+      return items;
+    },
+    async emitEvent(maxResults = false) {
+      const lastId = this._getLastId();
+      const fn = this.getFunction();
+
+      const response = this.sortItems(await fn());
+
+      let responseArray = [];
+      for (const item of response.reverse()) {
+        if (item.id == lastId) break;
+        responseArray.push(item);
+      }
+
+      if (responseArray.length) {
+        if (maxResults && (responseArray.length > maxResults)) {
+          responseArray.length = maxResults;
+        }
+        this._setLastId(responseArray[0].id);
+      }
+
+      for (const item of responseArray.reverse()) {
+        this.$emit(item, {
+          id: item.id,
+          summary: this.getSummary(item),
+          ts: Date.now(),
+        });
+      }
+    },
path: components/peerdom/sources/common/base.mjs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling and document required overrides**

The `emitEvent` method lacks error handling for API calls, and there's no documentation about the required methods that must be implemented by subclasses.


```diff
+   /**
+    * Override this method to return a function that fetches data from the API
+    * @returns {Function} A function that returns a Promise resolving to API data
+    */
+   getFunction() {
+     throw new Error("getFunction must be implemented by subclass");
+   },
+
+   /**
+    * Override this method to format a summary of the event
+    * @param {Object} item - The item to summarize
+    * @returns {String} A summary of the item
+    */
+   getSummary(item) {
+     throw new Error("getSummary must be implemented by subclass"); 
+   },

    async emitEvent(maxResults = false) {
      const lastId = this._getLastId();
      const fn = this.getFunction();

-     const response = this.sortItems(await fn());
+     try {
+       const { data } = await fn();
+       const response = this.sortItems(data);
      
-     let responseArray = [];
+       let responseArray = [];
        for (const item of response.reverse()) {
          if (item.id == lastId) break;
          responseArray.push(item);
        }

        if (responseArray.length) {
          if (maxResults && (responseArray.length > maxResults)) {
            responseArray.length = maxResults;
          }
          this._setLastId(responseArray[0].id);
        }

        for (const item of responseArray.reverse()) {
          this.$emit(item, {
            id: item.id,
            summary: this.getSummary(item),
            ts: Date.now(),
          });
        }
+     } catch (error) {
+       console.error("Error fetching or processing data:", error);
+       throw error;
+     }
    },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    /**
     * Override this method to return a function that fetches data from the API
     * @returns {Function} A function that returns a Promise resolving to API data
     */
    getFunction() {
      throw new Error("getFunction must be implemented by subclass");
    },

    /**
     * Override this method to format a summary of the event
     * @param {Object} item - The item to summarize
     * @returns {String} A summary of the item
     */
    getSummary(item) {
      throw new Error("getSummary must be implemented by subclass"); 
    },

    async emitEvent(maxResults = false) {
      const lastId = this._getLastId();
      const fn = this.getFunction();

      try {
        const { data } = await fn();
        const response = this.sortItems(data);

        let responseArray = [];
        for (const item of response.reverse()) {
          if (item.id == lastId) break;
          responseArray.push(item);
        }

        if (responseArray.length) {
          if (maxResults && (responseArray.length > maxResults)) {
            responseArray.length = maxResults;
          }
          this._setLastId(responseArray[0].id);
        }

        for (const item of responseArray.reverse()) {
          this.$emit(item, {
            id: item.id,
            summary: this.getSummary(item),
            ts: Date.now(),
          });
        }
      } catch (error) {
        console.error("Error fetching or processing data:", error);
        throw error;
      }
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T20:30:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-23T17:21:23Z
body: [vc]: #jQfqholkzwqPKaBnDkuZC8P+OqrMshnOkobFqHfRU4g=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJwaXBlZHJlYW0tZG9jcy1yZWRpcmVjdC1kby1ub3QtZWRpdCIsInJvb3REaXJlY3RvcnkiOiJkb2NzIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vcGlwZWRyZWFtZXJzL3BpcGVkcmVhbS1kb2NzLXJlZGlyZWN0LWRvLW5vdC1lZGl0LzVkU3ZTR3RqazhSUmpQRVRHMUpqeTh4WWNOV2QiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCJ9LHsibmFtZSI6ImRvY3MtdjIiLCJyb290RGlyZWN0b3J5IjoiZG9jcy12MiIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9waXBlZHJlYW1lcnMvZG9jcy12Mi82ZHhZd2FNdUhkTEhQZ0RnUGN4eGVLbU5DM3E1IiwicHJldmlld1VybCI6ImRvY3MtdjItZ2l0LWlzc3VlLTE2NDAwLXBpcGVkcmVhbWVycy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImRvY3MtdjItZ2l0LWlzc3VlLTE2NDAwLXBpcGVkcmVhbWVycy52ZXJjZWwuYXBwIn19LHsibmFtZSI6InBpcGVkcmVhbS1kb2NzIiwicm9vdERpcmVjdG9yeSI6ImRvY3MiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9waXBlZHJlYW1lcnMvcGlwZWRyZWFtLWRvY3MvQjhQN0o0cXVYYjNwUnhnRnFmQWY3UEh3U3lZRCIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **docs-v2** | ✅ Ready ([Inspect](https://vercel.com/pipedreamers/docs-v2/6dxYwaMuHdLHPgDgPcxxeKmNC3q5)) | [Visit Preview](https://docs-v2-git-issue-16400-pipedreamers.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/docs-v2-git-issue-16400-pipedreamers.vercel.app?via=pr-comment-feedback-link) | Apr 29, 2025 1:04pm |

<details><summary>2 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **pipedream-docs** | ⬜️ Ignored ([Inspect](https://vercel.com/pipedreamers/pipedream-docs/B8P7J4quXb3pRxgFqfAf7PHwSyYD)) |  |  | Apr 29, 2025 1:04pm |
| **pipedream-docs-redirect-do-not-edit** | ⬜️ Ignored ([Inspect](https://vercel.com/pipedreamers/pipedream-docs-redirect-do-not-edit/5dSvSGtjk8RRjPETG1Jjy8xYcNWd)) |  |  | Apr 29, 2025 1:04pm |
</details>




author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-23T17:21:28Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Review was skipped due to path filters
> 
> <details>
> <summary>:no_entry: Files ignored due to path filters (1)</summary>
> 
> * `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`
> 
> </details>
> 
> CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including `**/dist/**` will override the default block on the `dist` directory, by removing the pattern from both the lists.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces a comprehensive Peerdom integration, adding new actions, sources, utilities, and constants. It implements polling sources for detecting new roles and members, and actions for creating, updating, and assigning members to roles. The Peerdom app module now includes detailed property definitions and methods for interacting with the Peerdom API, such as listing, creating, and updating roles and members. Supporting utility and constant files are added, and each source includes a test event for validation. The package manifest is updated with a new dependency and version number.

## Changes

| File(s)                                                                                          | Change Summary                                                                                                                                                                                                                                                                                                                                                 |
|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `components/peerdom/actions/assign-member-to-role/assign-member-to-role.mjs`<br>`components/peerdom/actions/create-role/create-role.mjs`<br>`components/peerdom/actions/update-role/update-role.mjs` | Added new action modules for assigning a member to a role, creating a role, and updating a role, each implementing required props, API calls, and error handling.                                                                                                                |
| `components/peerdom/common/constants.mjs`                                                        | Introduced constants `LIMIT` (100) and `SHAPE_OPTIONS` (["circle", "hexagon"]).                                                                                                                                                                                                                                        |
| `components/peerdom/common/utils.mjs`                                                            | Added `parseObject` utility for parsing JSON strings or arrays into objects.                                                                                                                                                                                                                                           |
| `components/peerdom/package.json`                                                                | Updated version to 0.1.0 and added dependency on `@pipedream/platform`.                                                                                                                                                                                                                                                |
| `components/peerdom/peerdom.app.mjs`                                                             | Fully implemented the Peerdom app integration with prop definitions and methods for API interaction: listing, creating, updating roles, assigning members, and paginated fetching.                                                                                               |
| `components/peerdom/sources/common/base.mjs`                                                     | Added a base source module with polling, event emission, deduplication, and last ID tracking logic.                                                                                                                                                                              |
| `components/peerdom/sources/new-member/new-member.mjs`<br>`components/peerdom/sources/new-role/new-role.mjs` | Added new polling source modules for "New Member Added" and "New Role Created", each extending the base and implementing event summary and function references.                                                                           |
| `components/peerdom/sources/new-member/test-event.mjs`<br>`components/peerdom/sources/new-role/test-event.mjs` | Added test event fixtures for new member and new role sources, with representative data structures.                                                                                                                                                                              |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Action
    participant PeerdomApp
    participant PeerdomAPI

    User->>Action: Trigger action (e.g., create-role)
    Action->>PeerdomApp: Call method (e.g., createRole)
    PeerdomApp->>PeerdomAPI: Make HTTP request (POST/PUT)
    PeerdomAPI-->>PeerdomApp: Return response
    PeerdomApp-->>Action: Return result
    Action-->>User: Export summary and result
```

```mermaid
sequenceDiagram
    participant Source
    participant Timer
    participant PeerdomApp
    participant PeerdomAPI
    participant DB

    Timer->>Source: Trigger poll (run)
    Source->>DB: Get lastId
    Source->>PeerdomApp: Call list function (e.g., listPeers)
    PeerdomApp->>PeerdomAPI: Fetch data
    PeerdomAPI-->>PeerdomApp: Return data
    PeerdomApp-->>Source: Return data
    Source->>Source: Emit new events, update lastId
    Source->>DB: Set lastId
```

## Assessment against linked issues

| Objective                                                                                              | Addressed | Explanation                                                                                      |
|--------------------------------------------------------------------------------------------------------|-----------|--------------------------------------------------------------------------------------------------|
| Implement new-role polling source (detects new roles in a circle) [#16400]                             | ✅        |                                                                                                  |
| Implement new-member polling source (detects new members in a circle or assigned to a role) [#16400]   | ✅        |                                                                                                  |
| Implement create-role action (creates new role in specified circle) [#16400]                           | ✅        |                                                                                                  |
| Implement assign-member-to-role action (assigns member to role) [#16400]                               | ✅        |                                                                                                  |
| Implement update-role action (updates role attributes) [#16400]                                        | ✅        |                                                                                                  |

## Suggested labels

`User submitted`

## Poem

> A hop, a skip, a Peerdom leap,  
> New roles and members, no time for sleep!  
> Actions abound, with sources that poll,  
> Assign and update, we rabbits do it all.  
> Constants and utils, our toolkit grows,  
> With every commit, our garden glows!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=PipedreamHQ/pipedream&utm_content=16405):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-23T17:21:29Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Draft detected.
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=PipedreamHQ/pipedream&utm_content=16405):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: luancazarine
created_at: 2025-04-29T12:13:03Z
body: /approve
author_association: CONTRIBUTOR