{"id":21291899,"url":"https://github.com/idea2app/mobx-restful","last_synced_at":"2025-07-11T16:31:02.508Z","repository":{"id":46048712,"uuid":"312994805","full_name":"idea2app/MobX-RESTful","owner":"idea2app","description":"Common MobX abstract base Class \u0026 Decorator utilities for RESTful API, which makes it much easier to build Web apps, no matter it's a MPA, SPA, PWA, mini-apps or hybird-apps.","archived":false,"fork":false,"pushed_at":"2024-10-29T11:41:02.000Z","size":725,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-29T13:25:53.033Z","etag":null,"topics":["api","mobx","model","restful","sdk","state","store"],"latest_commit_sha":null,"homepage":"https://idea2app.github.io/MobX-RESTful/","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/idea2app.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":"2020-11-15T09:11:14.000Z","updated_at":"2024-10-29T11:36:49.000Z","dependencies_parsed_at":"2024-05-28T00:23:22.263Z","dependency_job_id":null,"html_url":"https://github.com/idea2app/MobX-RESTful","commit_stats":{"total_commits":19,"total_committers":1,"mean_commits":19.0,"dds":0.0,"last_synced_commit":"4b9afa2a3f7f51a25670961741c273f67900e7a8"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idea2app%2FMobX-RESTful","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idea2app%2FMobX-RESTful/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idea2app%2FMobX-RESTful/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idea2app%2FMobX-RESTful/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/idea2app","download_url":"https://codeload.github.com/idea2app/MobX-RESTful/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225741249,"owners_count":17516894,"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":["api","mobx","model","restful","sdk","state","store"],"created_at":"2024-11-21T13:46:39.960Z","updated_at":"2025-07-11T16:31:02.501Z","avatar_url":"https://github.com/idea2app.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MobX RESTful\n\nCommon [MobX][1] **abstract base Class \u0026 [Decorator][2]** utilities for [RESTful API][3].\n\nJust define your **Data models** \u0026 **Client HTTP methods**, then leave rest of things to MobX!\n\n[![MobX compatibility](https://img.shields.io/badge/Compatible-1?logo=mobx\u0026label=MobX%206%2F7)][1]\n[![NPM Dependency](https://img.shields.io/librariesio/github/idea2app/MobX-RESTful.svg)][4]\n[![CI \u0026 CD](https://github.com/idea2app/MobX-RESTful/actions/workflows/main.yml/badge.svg)][5]\n\n[![NPM](https://nodei.co/npm/mobx-restful.png?downloads=true\u0026downloadRank=true\u0026stars=true)][6]\n\n## Versions\n\n|  SemVer   |    status    | ES decorator |    MobX     |\n| :-------: | :----------: | :----------: | :---------: |\n| `\u003e=0.7.0` | ✅developing |   stage-3    |  `\u003e=6.11`   |\n| `\u003c0.7.0`  | ❌deprecated |   stage-2    | `\u003e=4 \u003c6.11` |\n\n## Usage\n\n### Installation\n\n```bash\nnpm install koajax mobx mobx-restful\n```\n\n### `tsconfig.json`\n\n```json\n{\n    \"compilerOptions\": {\n        \"target\": \"ES6\",\n        \"moduleResolution\": \"Node\",\n        \"useDefineForClassFields\": true,\n        \"experimentalDecorators\": false,\n        \"jsx\": \"react-jsx\"\n    }\n}\n```\n\n### Simple List\n\n#### `model/client.ts`\n\n```javascript\nimport { HTTPClient } from 'koajax';\n\nexport const client = new HTTPClient({\n    baseURI: 'https://api.github.com/',\n    responseType: 'json'\n});\n```\n\n#### `model/Repository.ts`\n\n```typescript\nimport { buildURLData } from 'web-utility';\nimport { Filter, ListModel } from 'mobx-restful';\nimport { components } from '@octokit/openapi-types';\n\nimport { client } from './client';\n\nexport type Organization = components['schemas']['organization-full'];\nexport type Repository = components['schemas']['minimal-repository'];\n\nexport class RepositoryModel\u003c\n    D extends Repository = Repository,\n    F extends Filter\u003cD\u003e = Filter\u003cD\u003e\n\u003e extends ListModel\u003cD, F\u003e {\n    client = client;\n    baseURI = 'orgs/idea2app/repos';\n\n    async loadPage(page: number, per_page: number) {\n        const { body } = await this.client.get\u003cD[]\u003e(\n            `${this.baseURI}?${buildURLData({ page, per_page })}`\n        );\n        const [_, organization] = this.baseURI.split('/');\n        const {\n            body: { public_repos }\n        } = await this.client.get\u003cOrganization\u003e(`orgs/${organization}`);\n\n        return { pageData: body, totalCount: public_repos };\n    }\n}\n\nexport default new RepositoryModel();\n```\n\n#### `page/Repository.tsx`\n\nUse [WebCell][10] as an Example\n\n```tsx\nimport { component, observer } from 'web-cell';\n\nimport repositoryStore from '../model/Repository';\n\n@component({ tagName: 'repository-page' })\n@observer\nexport class RepositoryPage extends HTMLElement {\n    connectedCallback() {\n        repositoryStore.getList();\n    }\n\n    disconnectedCallback() {\n        repositoryStore.clear();\n    }\n\n    render() {\n        const { currentPage } = repositoryStore;\n\n        return (\n            \u003cul\u003e\n                {currentPage.map(({ full_name, html_url }) =\u003e (\n                    \u003cli key={full_name}\u003e\n                        \u003ca target=\"_blank\" href={html_url}\u003e\n                            {full_name}\n                        \u003c/a\u003e\n                    \u003c/li\u003e\n                ))}\n            \u003c/ul\u003e\n        );\n    }\n}\n```\n\n### Preload List\n\n#### `model/PreloadRepository.ts`\n\n```typescript\nimport { buildURLData } from 'web-utility';\nimport { Buffer } from 'mobx-restful';\n\nimport { client } from './client';\nimport { Repository, RepositoryModel } from './Repository';\n\nexport class PreloadRepositoryModel extends Buffer\u003cRepository\u003e(\n    RepositoryModel\n) {\n    client = client;\n    baseURI = 'orgs/idea2app/repos';\n\n    loadPage = RepositoryModel.prototype.loadPage;\n}\n\nexport default new PreloadRepositoryModel();\n```\n\n### Multiple Source List\n\n#### `model/MultipleRepository.ts`\n\n```typescript\nimport { buildURLData, mergeStream } from 'web-utility';\nimport { Stream } from 'mobx-restful';\nimport { components } from '@octokit/openapi-types';\n\nimport { client } from './client';\nimport { Repository, RepositoryModel } from './Repository';\n\nexport type User = components['schemas']['public-user'];\n\nexport class MultipleRepository extends Stream\u003cRepository\u003e(RepositoryModel) {\n    declare baseURI: string;\n    client = client;\n\n    async *getOrgRepos() {\n        const {\n            body: { public_repos }\n        } = await this.client.get\u003cOrganization\u003e('orgs/idea2app');\n\n        this.totalCount = public_repos;\n\n        for (let i = 1; ; i++) {\n            const { body } = await this.client.get\u003cRepository[]\u003e(\n                'orgs/idea2app/repos?page=' + i\n            );\n            if (!body[0]) break;\n\n            yield* body;\n        }\n    }\n\n    async *getUserRepos() {\n        const {\n            body: { public_repos }\n        } = await this.client.get\u003cUser\u003e('users/TechQuery');\n\n        this.totalCount = public_repos;\n\n        for (let i = 1; ; i++) {\n            const { body } = await this.client.get\u003cRepository[]\u003e(\n                'users/TechQuery/repos?page=' + i\n            );\n            if (!body[0]) break;\n\n            yield* body;\n        }\n    }\n\n    openStream() {\n        return mergeStream(\n            this.getOrgRepos.bind(this),\n            this.getUserRepos.bind(this)\n        );\n    }\n}\n\nexport default new MultipleRepository();\n```\n\n### Data Persistence\n\n`@persist()` \u0026 `restore()` functions give us a declarative way to save \u0026 restore data to/from [IndexedBD][11], such as these following examples:\n\n#### User Session\n\n```ts\nimport { observable } from 'mobx';\nimport { BaseModel, persist, restore, toggle } from 'mobx-restful';\nimport { Day } from 'web-utility';\n\nimport { client } from './client';\nimport { User } from './User';\n\nexport class SessionModel extends BaseModel {\n    baseURI = 'session';\n    client = client;\n\n    @persist({ expireIn: 15 * Day })\n    @observable\n    user?: User;\n\n    restored = restore(this, 'Session');\n\n    @toggle('uploading')\n    async signIn(username: string, password: string) {\n        const { body } = await this.client.post\u003cUser\u003e('session', {\n            username,\n            password\n        });\n        return (this.user = body);\n    }\n}\n\nexport default new Session();\n```\n\n#### File Downloader\n\nThis module has been moved to [MobX-downloader][12] since MobX-RESTful v2.\n\n#### List Cache\n\n##### `model/Party/Gift.ts`\n\n```ts\nimport { ListModel, persistList } from 'mobx-restful';\n\nimport { Gift } from '@my-scope/service-type';\n\nimport { client } from './client';\n\n@persistList({\n    storeKey: ({ partyId }) =\u003e `PartyGift-${partyId}`,\n    expireIn: 2 * Day\n})\nexport class PartyGiftModel extends ListModel\u003cGift\u003e {\n    baseURI = 'party';\n    client = client;\n\n    constructor(public partyId: number) {\n        super();\n        this.baseURI += `/${partyId}/gift`;\n    }\n\n    protected async loadPage(pageIndex: number, pageSize: number) {\n        const { body } = await this.client.get\u003c{\n            count: number;\n            list: Gift[];\n        }\u003e(`${this.baseURI}?${buildURLData({ pageIndex, pageSize })}`);\n\n        return { pageData: body.list, totalCount: body.count };\n    }\n}\n```\n\n##### `page/Party/Gift.tsx`\n\nThis example page uses [Cell Router][13] to pass in `partyId` route parameter:\n\n```tsx\nimport { observable } from 'mobx';\nimport { component, observer, attribute } from 'web-cell';\n\nimport { PartyGiftModel } from '../../model/Party/Gift';\n\n@component({ tagName: 'party-gift-page' })\n@observer\nexport class PartyGiftPage extends HTMLElement {\n    @attribute\n    @observable\n    accessor partyId = 0;\n\n    @observable\n    accessor store: PartyGiftModel | undefined;\n\n    async connectedCallback() {\n        this.store = new PartyGiftModel(this.partyId);\n\n        await this.store.restored;\n        // this calling will do nothing after the first loading\n        // in list cache period\n        await this.store.getAll();\n    }\n\n    render() {\n        const { allItem } = this.store || {};\n\n        return (\n            \u003c\u003e\n                \u003ch1\u003eGift wall\u003c/h1\u003e\n                \u003col\u003e\n                    {allItem.map(({ name, price }) =\u003e (\n                        \u003cli key={name}\u003e\n                            {name} - {price}\n                        \u003c/li\u003e\n                    ))}\n                \u003c/ol\u003e\n            \u003c/\u003e\n        );\n    }\n}\n```\n\n## Wrapper\n\n1. [Strapi v4/5](https://github.com/idea2app/MobX-RESTful/blob/main/wrapper/Strapi)\n2. [GitHub](https://github.com/idea2app/MobX-GitHub)\n3. [Lark/FeiShu](https://github.com/idea2app/MobX-Lark)\n\n## Component\n\n1. [Table, List \u0026 Form suite](https://github.com/idea2app/MobX-RESTful-table)\n\n## Scaffold\n\n1.  Client-side Rendering (React): https://github.com/idea2app/React-MobX-Bootstrap-ts\n2.  Client-side Rendering (Vue): https://github.com/idea2app/Vue-MobX-Prime-ts\n3.  Server-side Rendering (React): https://github.com/idea2app/Next-Bootstrap-ts\n4.  Cross-end App (React): https://github.com/idea2app/Taro-Vant-MobX-ts\n\n## Limitation\n\n- [ ] [`abstract` hint of Mixin is missing][14]\n\n[1]: https://mobx.js.org/\n[2]: https://github.com/tc39/proposal-decorators\n[3]: https://en.wikipedia.org/wiki/Representational_state_transfer\n[4]: https://libraries.io/npm/mobx-restful\n[5]: https://github.com/idea2app/MobX-RESTful/actions/workflows/main.yml\n[6]: https://nodei.co/npm/mobx-restful/\n[7]: https://yarnpkg.com/\n[8]: https://pnpm.io/\n[9]: https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/\n[10]: https://github.com/EasyWebApp/WebCell\n[11]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API\n[12]: https://github.com/idea2app/MobX-downloader\n[13]: https://github.com/EasyWebApp/cell-router\n[14]: https://github.com/microsoft/TypeScript/issues/39752#issuecomment-1239810720\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidea2app%2Fmobx-restful","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fidea2app%2Fmobx-restful","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidea2app%2Fmobx-restful/lists"}