-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 58c4fd2
Showing
149 changed files
with
40,619 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Logs | ||
logs | ||
*.log | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directory | ||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git | ||
node_modules | ||
|
||
lib/ | ||
|
||
book/_book | ||
doc/ | ||
hooks | ||
platforms | ||
plugins | ||
ionic.project | ||
config.xml | ||
www | ||
.tmp | ||
|
||
.DS_Store | ||
|
||
.idea/ | ||
public/app.dist.less | ||
|
||
# Intellij project configration | ||
account-wx-service.iml | ||
|
||
typings | ||
.tsdrc | ||
jsconfig.json | ||
coverage | ||
.vscode | ||
dist | ||
spec-js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
language: node_js | ||
node_js: | ||
- "4.4" | ||
|
||
install: | ||
- npm link | ||
- npm install | ||
- typings install | ||
- npm run lint | ||
- npm link teambition-sdk | ||
|
||
before_script: | ||
- "npm run build_all" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# 目录结构 | ||
``` | ||
├── node_modules | ||
├── package.json | ||
├── tsconfig.json | ||
├── tslint.json | ||
├── typings.json | ||
├── mock | ||
│ ├── backend.ts | ||
│ ├── index.ts | ||
│ ├── mock.ts | ||
│ ├── response.ts | ||
│ ├── utils.ts | ||
├── src | ||
│ ├── apis | ||
│ ├── decorators | ||
│ ├── fetchs | ||
│ ├── models | ||
│ ├── schemas | ||
│ ├── sockets | ||
│ ├── storage | ||
│ ├── utils | ||
│ ├── app.ts | ||
│ ├── SocketApp.ts | ||
│ ├── teambition.ts | ||
├── test | ||
│ ├── unit | ||
│ ├── mock | ||
│ └── e2e | ||
``` | ||
|
||
# 程序结构 | ||
## 获取数据 | ||
以获取一条任务数据为例 | ||
首先调用 `TaskAPI.get(taskId)` 方法,API 内的这个方法会首先判断是否有已经缓存了的此条 `task`。第一次调用这个接口时并不存在缓存的此条 `task`,则通过 `TaskFetch.get(taskId)` 方法通过向服务器请求得到数据: | ||
|
||
```json | ||
{ | ||
"_id": "569dc0cb0fafba0857158d84", | ||
"_creatorId": "56986d43542ce1a2798c8cfb", | ||
"_executorId": "56986d43542ce1a2798c8cfb", | ||
"_projectId": "56988fb705ead4ae7bb8dcfe", | ||
"_tasklistId": "56988fb7644284a37be3ba6f", | ||
"tagIds": [], | ||
"_stageId": "56988fb7644284a37be3ba72", | ||
"visiable": "members", | ||
"visible": "members", | ||
"involveMembers": [ | ||
"56986d43542ce1a2798c8cfb" | ||
], | ||
"updated": "2016-02-25T09:27:16.258Z", | ||
"created": "2016-01-19T04:51:23.040Z", | ||
"isArchived": "false", | ||
"isDone": false, | ||
"priority": 1, | ||
"dueDate": null, | ||
"accomplished": null, | ||
"note": "", | ||
"content": "加拉克苏斯大王", | ||
"_sourceId": null, | ||
"sourceDate": null, | ||
"subtasks": [], | ||
"commentsCount": 0, | ||
"attachmentsCount": 0, | ||
"likesCount": 0, | ||
"objectlinksCount": 0, | ||
"subtaskCount": { | ||
"total": 0, | ||
"done": 0 | ||
}, | ||
"creator": { | ||
"name": "龙逸楠", | ||
"avatarUrl": "", | ||
"_id": "56986d43542ce1a2798c8cfb" | ||
}, | ||
"stage": { | ||
"name": "影月谷", | ||
"_id": "56988fb7644284a37be3ba72" | ||
}, | ||
"executor": { | ||
"name": "龙逸楠", | ||
"avatarUrl": "", | ||
"_id": "56986d43542ce1a2798c8cfb" | ||
}, | ||
"isFavorite": false | ||
} | ||
``` | ||
|
||
在从服务器取到这条数据后,它将会被传给相应的数据模型处理,这里是 `TaskModel.addOne`。在 `TaskModel.addOne` 方法中, 数据被转换成对应的 `Schema`对象,并存入 `Database` 中。在存入 `Database` 的过程中,这个 `Schema` 上所有带 `_id` 的子对象(也可指定为其它 key )将会被索引并存储,以便后续的更新。在存储的过程中,这些对象(TaskSchema 对象以及它的子对象)被转换成 `Database` 中的 `Model` 对象。并在 `Model` 中存储相应的 `parents` 与 `children` 索引,以便后续更新时能通知到与之关联的对象。 | ||
|
||
存储完成后,`Database` 返回一个持续发射信号的 `Observable` 对象, 这个 `Observable` 是 *Storage* 目录下的 `Model` 对象返回的, 将会被返回给 API 调用者。 | ||
|
||
## 更新 | ||
当这条已经被缓存的任务被更新时(通过网络请求 或者 Socket), 更新者通过将 `patch` 传入相应的 `Model` 的 `update` 方法中,这里是 `TaskModel.update(id, patch)`。而 `Model` 会调用 `Database` 中的 `updateOne` 方法。 | ||
|
||
`Database` 在更新数据时,会产生这样几个流: | ||
- 通知自身 `Model` 更新的流,通过这个流只要订阅过这条数据的地方都会收到更新后的对象拷贝 | ||
- 通知它的 `parents` 更新的流,通过这个流任何包含了这个对象的父对象会收到更新后的对象拷贝 | ||
- 判断更新后的数据是否满足存有此类 `Schema` 的 `Collection` 的条件的流。比如变更了 `Tasklistid` 后应该不满足先前存储它的 `Collecion` 条件但会变得满足进入另一个 `Collection` 的条件。 | ||
- 判断变更后数据是否符合原来存储它的 `Collection` 条件的流。 | ||
|
||
在 `Database` 中,这几个流被 `merge` 到一起。在 `Database.updateOne` 被调用时 ,会返回一个 cold `Observable` 对象,这个 `Observable` 对象被订阅时,之前 merge 到一起的各个流会被直接 `forEach` ,并且它的值会通过 `Observable` 发射出去。 | ||
|
||
所以通过 `API` 更新一个数据后的流是一个 `cold signal`,只会收到一次通知。 | ||
|
||
## 删除 | ||
当调用 `API` 上相关的删除方法时,`API` 会通过相应的数据模型调用 `Database` 上的 `delete` 方法。 | ||
|
||
Database 在删除一条任务时,会产生这样几个流: | ||
|
||
- 从这个 Model 的 `children` 上的 `parent` 把自己的索引删除的流。因为它被从缓存中移除,所以任何它原来的 `children` 的变更都不应该再通知它。 | ||
- 从这个 Model 的 `parent` 上的 `children` 把自己的索引删除的流。因为它被从缓存中移除,它不会再有变更信息会被通知给它原来的 `parent`。 | ||
- 将 Model 从包含它的 `Collection` 中删除的流。它包含在哪个 `Collection` 是在存储 `Collection` 的过程中或 `Model` update 的过程中在 `Model` 上建立索引的。 | ||
|
||
这个三个流在 `Database.delete` 中被 `merge` 成一个流,并在 `delete` 方法返回的 `Observable` 被订阅时被订阅。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
# isomorphic-sdk for Teambition APIs [](https://travis-ci.org/teambition/teambition-sdk) | ||
|
||
## 设计理念 | ||
|
||
SDK 主要解决的是数据同步的问题。通俗点讲,就是在前端使用数据模型模拟出数据库的增删改查等操作。 | ||
|
||
为什么会有这种需求? 以 `https://api.teambition.com/tasks/:_id` 为例, Teambition 的 API 会返回下面格式的数据: | ||
|
||
```json | ||
{ | ||
"_id": "001", | ||
"name": "task", | ||
"executor": { | ||
"_id": "002", | ||
"name": "executor 1", | ||
"avatarUrl": "https://xxx" | ||
}, | ||
"subtasks": [ | ||
{ | ||
"_id": "003", | ||
"name": "subtask", | ||
"executor": { | ||
"_id": "004", | ||
"name": "executor 2", | ||
"avatarUrl": "https://xxx" | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
|
||
而倘若这个任务中包含的子对象,比如 `executor` 字段对应的数据通过其它 API 进行了变更: | ||
|
||
```ts | ||
/** | ||
* @url https://api.teambition.com/subtasks/:_id | ||
* @method put | ||
* @body {name: 'executor test'} | ||
*/ | ||
SubtasksAPI.update('002', { | ||
name: 'subtask update' | ||
}) | ||
.subscribe() | ||
``` | ||
|
||
在前端,需要自行处理与此 subtask 相关的所有变更情况。例如: | ||
|
||
1. 包含这个子任务的列表中的这个子任务名字的变更。 | ||
2. 包含这个子任务的任务的详情页中,该子任务名字的变更。 | ||
|
||
然而在现有的 Teambition 数据模型中,需要在每一个 `Model` 或者 `Collection` 或者 `View` 中手动监听与自己相关联的数据,例如: | ||
|
||
```js | ||
// 匹配第一种情况 | ||
class MyTasksView extends Backbone.View { | ||
... | ||
listen() { | ||
this.listenTo(warehouse, ':change:task', model => { | ||
// handler | ||
}) | ||
this.listenTo(warehouse, ':change:subtask', model => { | ||
// handler | ||
}) | ||
} | ||
} | ||
``` | ||
|
||
```js | ||
// 匹配第二种情况 | ||
|
||
class SubtaskCollection extends Backbone.Collection { | ||
... | ||
|
||
constructor() { | ||
this.on('add destroy remove change:isDone', () => | ||
Socket.trigger(`:change:task/${this._boundToObjectId}`, { | ||
subtaskCount: { | ||
total: this.length | ||
done: this.getDoneSubTaskCount() | ||
} | ||
}) | ||
) | ||
} | ||
getDoneSubTaskCount() { | ||
this.where({isDone: true}).length | ||
} | ||
} | ||
|
||
class TaskView extends Backbone.View { | ||
... | ||
listen() { | ||
this.listenTo(this.taskModel, 'change', this.render) | ||
} | ||
} | ||
``` | ||
|
||
而在当前的设计中,所有的这种变更情况都在数据层处理,视图/业务 层只需要订阅一个数据源,这个数据源随后的所有变更都会通知到订阅者。 | ||
比如获取一个任务: | ||
|
||
```ts | ||
import {TasksAPI, TaskSchema} from 'teambition-sdk' | ||
import {Card} from 'teambition-ui' | ||
import Route from '../route' | ||
import {errorHandler} from '../errorHandler' | ||
|
||
@component({ | ||
providers: [Route, TasksAPI], | ||
template: require('./templates/index.vue'), | ||
directives: [Card] | ||
}) | ||
class TaskView { | ||
|
||
private task: TaskSchema | ||
|
||
constructor(private route: Route, private TasksAPI: TasksAPI) {} | ||
|
||
onInit() { | ||
const taskId = this.route.params._id | ||
TasksAPI.get(taskId) | ||
.subscribe(task => { | ||
this.task = task | ||
}, errorHandler) | ||
} | ||
} | ||
``` | ||
|
||
在这种场景下,关于 task 的任何变更 (tasklist 变更,executor 变更,stage 变更等等) 都会触发 subscribe 中的回调,从而简化 View 层的逻辑。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
'use strict' | ||
import { HttpResponse } from './response' | ||
import { fetchStack, restore, mockFetch } from './mock' | ||
import { forEach } from './utils' | ||
|
||
export const flushState = { | ||
flushed: false | ||
} | ||
|
||
export class Backend { | ||
|
||
constructor() { | ||
flushState.flushed = false | ||
mockFetch() | ||
} | ||
|
||
whenGET(uri: string) { | ||
return new HttpResponse(uri.toLowerCase(), 'get') | ||
} | ||
|
||
whenPUT(uri: string, data?: any) { | ||
return new HttpResponse(uri.toLowerCase(), 'put', data) | ||
} | ||
|
||
whenPOST(uri: string, data?: any) { | ||
return new HttpResponse(uri.toLowerCase(), 'post', data) | ||
} | ||
|
||
whenDELETE(uri: string) { | ||
return new HttpResponse(uri.toLowerCase(), 'delete') | ||
} | ||
|
||
flush() { | ||
forEach(fetchStack, (value: any, key: string) => { | ||
forEach(value.flushQueue, (resolves: any[]) => { | ||
resolves[0](resolves[1]) | ||
}) | ||
}) | ||
flushState.flushed = true | ||
} | ||
|
||
restore(): void { | ||
restore() | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './mock' | ||
export * from './backend' |
Oops, something went wrong.