{"id":27184264,"url":"https://github.com/teambition/teambition-sdk","last_synced_at":"2025-05-16T10:07:35.146Z","repository":{"id":3027371,"uuid":"48221935","full_name":"teambition/teambition-sdk","owner":"teambition","description":"Isomorphic JavaScript SDK for Teambition APIs","archived":false,"fork":false,"pushed_at":"2023-08-09T04:46:17.000Z","size":3381,"stargazers_count":432,"open_issues_count":58,"forks_count":55,"subscribers_count":28,"default_branch":"release","last_synced_at":"2025-05-04T23:27:13.801Z","etag":null,"topics":["lovefield","reactivedb","rxjs","teambition","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teambition.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-18T07:52:09.000Z","updated_at":"2025-05-04T00:12:15.000Z","dependencies_parsed_at":"2024-06-19T01:28:31.390Z","dependency_job_id":"05d298b9-ab24-4251-b294-c794dc112a6c","html_url":"https://github.com/teambition/teambition-sdk","commit_stats":{"total_commits":903,"total_committers":41,"mean_commits":"22.024390243902438","dds":0.6644518272425249,"last_synced_commit":"ba8dd830dcef6fe2b26c6b60985c552560f11710"},"previous_names":[],"tags_count":712,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambition%2Fteambition-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambition%2Fteambition-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambition%2Fteambition-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambition%2Fteambition-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teambition","download_url":"https://codeload.github.com/teambition/teambition-sdk/tar.gz/refs/heads/release","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253404931,"owners_count":21903108,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lovefield","reactivedb","rxjs","teambition","typescript"],"created_at":"2025-04-09T16:38:33.053Z","updated_at":"2025-05-16T10:07:30.131Z","avatar_url":"https://github.com/teambition.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"[![CircleCI](https://circleci.com/gh/teambition/teambition-sdk/tree/release.svg?style=svg)](https://circleci.com/gh/teambition/teambition-sdk/tree/release)\n[![Coverage Status](https://coveralls.io/repos/github/teambition/teambition-sdk/badge.svg?branch=release)](https://coveralls.io/github/teambition/teambition-sdk?branch=release)\n[![Dependency Status](https://david-dm.org/teambition/teambition-sdk.svg)](https://david-dm.org/teambition/teambition-sdk)\n[![devDependency Status](https://david-dm.org/teambition/teambition-sdk/dev-status.svg)](https://david-dm.org/teambition/teambition-sdk?type=dev)\n\n# isomorphic-sdk for Teambition APIs\n\n[![Greenkeeper badge](https://badges.greenkeeper.io/teambition/teambition-sdk.svg)](https://greenkeeper.io/)\n\n## 安装\n\n```bash\nnpm install teambition-sdk --save-dev\n```\n如果要求在不支持 Fetch API 的浏览器运行，请安装 polyfill 如 [whatwg-fetch](https://github.com/github/fetch)。\n\n## 开发\n\n目前主分支是 `release`，而 `master` 只应用于部分处于维护状态的老项目。常用命令如：\n\n```bash\nyarn               # 安装依赖\nnpm run build_test # 构建测试\nnpm test           # 跑一遍完整测试\nnpm watch          # 在开发时，以监听模式跑测试，每次代码修改都会重跑测试，帮助及时发现问题\n```\n\n## 发布\n注意目前发布要求使用 NPM 二步验证，请参考[文档](https://docs.npmjs.com/configuring-two-factor-authentication#sending-a-one-time-password-from-the-command-line)，在发布命令后面跟随 `--otp=验证码`。\n```bash\n# only publish sdk\nnpm run preversion   # 查询 npm 官方库，获取当前发布的最新正式版本和最高的预发版本号，避免打版本时出错\nnpm version v0.12.68 # 打正式版本 0.12.68（包括创建相应标签）\nnpm version v0.12.69-alpha.0-readme # 打预发版本 0.12.69-alpha.0-readme（包括创建相应标签）\nnpm run publish_sdk  # 在本地 build 当前代码并发布到 npm 官方库\n# 完成发布后，推荐将相应标签（tag）推到远端，如 `git push origin v0.12.69-alpha.0-readme`\n\n# publish sdk, mock and socket\nnpm version xxx\nnpm run publish_all\n```\n\n## 设计理念\n\nSDK 主要解决的是数据同步的问题。通俗点讲，就是在前端使用数据模型模拟出数据库的增删改查等操作。\n\n为什么会有这种需求? 以 `https://api.teambition.com/tasks/:_id` 为例， Teambition 的 API 会返回下面格式的数据:\n\n```json\n{\n  \"_id\": \"001\",\n  \"name\": \"task\",\n  \"executor\": {\n    \"_id\": \"002\",\n    \"name\": \"executor 1\",\n    \"avatarUrl\": \"https://xxx\"\n  },\n  \"subtasks\": [\n    {\n      \"_id\": \"003\",\n      \"name\": \"subtask\",\n      \"executor\": {\n        \"_id\": \"004\",\n        \"name\": \"executor 2\",\n        \"avatarUrl\": \"https://xxx\"\n      }\n    }\n  ]\n}\n```\n\n而倘若这个任务中包含的子对象，比如 `executor` 字段对应的数据通过其它 API 进行了变更:\n\n```ts\n/**\n * @url https://api.teambition.com/subtasks/:_id\n * @method put\n * @body {name: 'executor test'}\n */\nSubtasksAPI.update('002', {\n  name: 'subtask update'\n})\n  .subscribe()\n```\n\n在前端，需要自行处理与此 subtask 相关的所有变更情况。例如:\n\n1. 包含这个子任务的列表中的这个子任务名字的变更。\n2. 包含这个子任务的任务的详情页中，该子任务名字的变更。\n\n然而在现有的 Teambition 数据模型中，需要在每一个 `Model` 或者 `Collection` 或者 `View` 中手动监听与自己相关联的数据，例如:\n\n```js\n// 匹配第一种情况\nclass MyTasksView extends Backbone.View {\n  ...\n  listen() {\n    this.listenTo(warehouse, ':change:task', model =\u003e {\n      // handler\n    })\n    this.listenTo(warehouse, ':change:subtask', model =\u003e {\n      // handler\n    })\n  }\n}\n```\n\n```js\n// 匹配第二种情况\n\nclass SubtaskCollection extends Backbone.Collection {\n  ...\n\n  constructor() {\n    this.on('add destroy remove change:isDone', () =\u003e\n      Socket.trigger(`:change:task/${this._boundToObjectId}`, {\n        subtaskCount: {\n          total: this.length\n          done: this.getDoneSubTaskCount()\n        }\n      })\n    )\n  }\n  getDoneSubTaskCount() {\n    this.where({isDone: true}).length\n  }\n}\n\nclass TaskView extends Backbone.View {\n  ...\n  listen() {\n    this.listenTo(this.taskModel, 'change', this.render)\n  }\n}\n```\n\n而在当前的设计中，所有的这种变更情况都在数据层处理，视图/业务 层只需要订阅一个数据源，这个数据源随后的所有变更都会通知到订阅者。\n比如获取一个任务:\n\n```ts\nimport 'rxjs/add/operator/distinctUntilKeyChanged'\nimport 'teambition-sdk/apis/task'\nimport { SDK } from 'teambition-sdk/SDK'\nimport { Component, Input } from '@angular/core'\n\n@Component({\n  selector: 'task-detail',\n  template: `\n    \u003cdiv\u003e {{ task$?.name | async }} \u003c/div\u003e\n    \u003cdiv\u003e {{ subtaskCount$ | async }} \u003c/div\u003e\n  `\n})\nexport default class TaskView {\n\n  @Input('taskId') taskId: string\n\n  private task$ = this.SDK.getTask(this.taskId)\n    .publishReplay(1)\n    .refCount()\n\n  private subtaskCount$ = this.task$\n    .distinctUntilKeyChanged('subtasks')\n    .map(task =\u003e ({\n      total: task.subtasks.length,\n      done: task.subtasks.filter(x =\u003e x.isDone).length\n    }))\n}\n```\n\n\n如果更加纯粹的使用 RxJS，甚至可以组合多种数据和业务:\n\n\n```ts\nimport 'rxjs/add/operator/distinctUntilKeyChanged'\nimport 'rxjs/add/operator/distinctUntilChanged'\nimport 'teambition-sdk/apis/permission'\nimport 'teambition-sdk/apis/task'\nimport 'teambition-sdk/apis/project'\nimport { SDK } from 'teambition-sdk'\nimport { Component, Input } from '@angular/core'\nimport * as moment from 'moment'\nimport { errorHandler } from '../errorHandler'\n\n@Component({\n  selector: 'task-detail',\n  template: `\n    \u003cdiv [ngClass]=\"{'active': permission$.canEdit | async}\"\u003e\u003c/div\u003e\n    \u003cdiv\u003e {{ task$?.name | async }} \u003c/div\u003e\n    \u003cdiv\u003e {{ subtaskCount$ | async }} \u003c/div\u003e\n    \u003cdiv\u003e {{ dueDate$ | async }} \u003c/div\u003e\n  `\n})\nexport default class TaskView {\n\n  @Input('taskId') taskId: string\n\n  private task$ = SDK.getTask(this.taskId)\n    .catch(err =\u003e errorHandler(err))\n    .publishReplay(1)\n    .refCount()\n\n  private subtaskCount$ = this.task$\n    .distinctUntilKeyChanged('subtasks')\n    .map(task =\u003e ({\n      total: task.subtasks.length,\n      done: task.subtasks.filter(x =\u003e x.isDone).length\n    }))\n\n  private dueDate$ = this.task$\n    .map(task =\u003e moment(task.dueDate).format())\n\n  private project$ = this.task$\n    .distinctUntilKeyChanged('_projectId')\n    .switchMap(task =\u003e SDK.getProject(task._projectId))\n    .catch(err =\u003e errorHandler(err))\n    .publishReplay(1)\n    .refCount()\n\n  private permission$ = this.task$\n    .distinctUntilChanged((before, after) =\u003e {\n      return before._executorId === after._executorId \u0026\u0026\n        before._projectId === after._projectId\n    })\n    .switchMap(task =\u003e {\n      return this.project$\n        .distinctUntilKeyChanged('_defaultRoleId')\n        .switchMap(project =\u003e {\n          return SDK.getPermission(task, project)\n        })\n    })\n    .catch(err =\u003e errorHandler(err))\n    .publishReplay(1)\n    .refCount()\n```\n\n在这种场景下，关于 task 的任何变更 (tasklist 变更，executor 变更，stage 变更等等，权限变化) 都能让相关的数据自动更新，从而简化 View 层的逻辑。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteambition%2Fteambition-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteambition%2Fteambition-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteambition%2Fteambition-sdk/lists"}