├── .github └── workflows │ └── unit-tests.yml ├── .prettierignore ├── LICENSE ├── README.md ├── examples ├── basic │ ├── .codesandbox │ │ └── template.json │ ├── .gitignore │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ └── index.tsx │ ├── tsconfig.json │ └── vite.config.ts └── table-filters │ ├── .codesandbox │ └── template.json │ ├── .gitignore │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── components │ │ ├── App.tsx │ │ ├── InvoiceList.tsx │ │ ├── InvoiceListFilters.tsx │ │ └── InvoicePreview.tsx │ ├── context.ts │ ├── index.css │ ├── index.tsx │ ├── models │ │ ├── models.ts │ │ ├── queries.ts │ │ ├── services.ts │ │ └── stores.ts │ ├── router.tsx │ ├── server │ │ ├── data │ │ │ ├── acme.ts │ │ │ └── data.ts │ │ └── index.ts │ └── styles.css │ ├── tsconfig.json │ └── vite.config.ts ├── packages ├── mst-query-generator │ ├── .gitignore │ ├── generator │ │ ├── config.ts │ │ ├── generate.ts │ │ └── scaffold.ts │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── RootTypeOverride.ts │ │ ├── field-handler-enum.ts │ │ ├── field-handler-interface-union.ts │ │ ├── field-handler-list.ts │ │ ├── field-handler-object.ts │ │ ├── field-handler-scalar.ts │ │ ├── field-handler.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── models │ │ │ ├── Arg.ts │ │ │ ├── Config.ts │ │ │ ├── Directive.ts │ │ │ ├── EnumValue.ts │ │ │ ├── Field.ts │ │ │ ├── FieldOverride.ts │ │ │ ├── GeneratedField.ts │ │ │ ├── GeneratedFile.ts │ │ │ ├── InputField.ts │ │ │ ├── InterfaceOrUnionTypeResult.ts │ │ │ ├── Kind.ts │ │ │ ├── Overrides.ts │ │ │ ├── RootType.ts │ │ │ ├── Schema.ts │ │ │ ├── Specificity.ts │ │ │ ├── Type.ts │ │ │ ├── TypeResolver.ts │ │ │ └── index.ts │ │ ├── type-handler-enum.ts │ │ ├── type-handler-interface-union.ts │ │ ├── type-handler-object.ts │ │ ├── type-handler.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tests │ │ ├── __snapshots__ │ │ │ ├── type-resolver.test.ts.snap │ │ │ └── typeResolver.test.ts.snap │ │ ├── field-handler.test.ts │ │ ├── filter-types.test.ts │ │ ├── generate.test.ts │ │ ├── override.test.ts │ │ ├── scaffold.test.ts │ │ ├── schema │ │ │ ├── abstractTypes.graphql │ │ │ ├── todos.graphql │ │ │ └── unionTypes.graphql │ │ ├── type-handler.test.ts │ │ └── type-resolver.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.ts └── mst-query │ ├── .gitignore │ ├── .prettierignore │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── MstQueryHandler.ts │ ├── QueryClient.ts │ ├── QueryClientProvider.tsx │ ├── QueryStore.ts │ ├── create.ts │ ├── hooks.ts │ ├── index.ts │ ├── merge.ts │ ├── stores.ts │ └── utils.ts │ ├── tests │ ├── api │ │ ├── api.ts │ │ └── data.ts │ ├── models │ │ ├── AddItemMutation.ts │ │ ├── ArrayQuery.ts │ │ ├── ErrorMutation.ts │ │ ├── ItemModel.ts │ │ ├── ItemQuery.ts │ │ ├── ItemQueryWithOptionalRequest.ts │ │ ├── ListModel.ts │ │ ├── ListQuery.ts │ │ ├── RemoveItemMutation.ts │ │ ├── RootStore.ts │ │ ├── SafeReferenceQuery.ts │ │ ├── SetDescriptionMutation.ts │ │ ├── UnionModel.ts │ │ └── UserModel.ts │ ├── mstQuery.test.tsx │ └── utils │ │ ├── index.ts │ │ └── utils.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.ts └── prettier.config.js /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | 13 | test: 14 | name: Test 15 | runs-on: ubuntu-latest 16 | steps: 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 18 22 | 23 | - name: Checkout repository 24 | uses: actions/checkout@v2 25 | 26 | - name: Install dependencies for mst-query 27 | working-directory: ./packages/mst-query 28 | run: npm install 29 | 30 | - name: Run mst-query test 31 | working-directory: ./packages/mst-query 32 | run: npm run test 33 | 34 | - name: Install dependencies for mst-query-generator 35 | working-directory: ./packages/mst-query-generator 36 | run: npm install 37 | 38 | - name: Run mst-query-generator test 39 | working-directory: ./packages/mst-query-generator 40 | run: npm run test 41 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | LICENSE -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Conrab Opto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Query library for mobx-state-tree 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | # Features 10 | 11 | - Automatic Normalization 12 | - Garbage Collection 13 | - Infinite Scroll + Pagination Queries 14 | - Optimistic Mutations 15 | - Request Argument Type Validation 16 | - Abort Requests 17 | - Generate Models From Graphql Schema 18 | 19 | # Examples 20 | 21 | - [Basic](https://codesandbox.io/p/devbox/mst-query-basic-example-nk49ds?file=%2Fsrc%2Findex.tsx) 22 | - [Table Filters](https://codesandbox.io/p/devbox/mst-query-table-filters-example-2j3h3v?file=%2Fsrc%2Findex.tsx%3A18%2C26) 23 | 24 | # Basic Usage 25 | 26 | First, create a query... 27 | 28 | ```ts 29 | import { createQuery, createModelStore } from 'mst-query'; 30 | 31 | const MessageQuery = createQuery('MessageQuery', { 32 | data: types.reference(MessageModel), 33 | request: types.model({ id: types.string }), 34 | endpoint({ request }) { 35 | return fetch(`messages/${request.id}`).then((res) => res.json()); 36 | }, 37 | }); 38 | ``` 39 | 40 | ...then use the query in a React component! 41 | 42 | ```tsx 43 | const MesssageView = observer((props) => { 44 | const { id, messageStore } = props; 45 | const { data, error, isLoading } = useQuery(messageStore.messageQuery, { 46 | request: { id }, 47 | }); 48 | if (error) { 49 | return
An error occured...
; 50 | } 51 | if (!data) { 52 | return
Loading...
; 53 | } 54 | return
{data.message}
; 55 | }); 56 | ``` 57 | 58 | # Documentation 59 | 60 | ## Installation 61 | 62 | ``` 63 | npm install --save mst-query mobx-state-tree 64 | ``` 65 | 66 | ## Configuration 67 | 68 | ```tsx 69 | import { createModelStore, createRootStore, QueryClient, createContext } from 'mst-query'; 70 | 71 | const MessageQuery = createQuery('MessageQuery', { 72 | data: types.reference(MessageModel), 73 | request: types.model({ id: types.string }), 74 | endpoint({ request }) { 75 | return fetch(`messages/${request.id}`).then((res) => res.json()); 76 | }, 77 | }); 78 | 79 | const MessageStore = createModelStore('MessageStore', MessageModel).props({ 80 | messageQuery: types.optional(MessageQuery, {}), 81 | }); 82 | 83 | const RootStore = createRootStore({ 84 | messageStore: types.optional(MessageStore, {}), 85 | }); 86 | 87 | const queryClient = new QueryClient({ RootStore }); 88 | const { QueryClientProvider, useRootStore } = createContext(queryClient); 89 | 90 | function App() { 91 | return ( 92 | 93 | 94 | 95 | ); 96 | } 97 | ``` 98 | 99 | ## Queries 100 | 101 | ### `createQuery` 102 | 103 | ```tsx 104 | import { types } from 'mobx-state-tree'; 105 | import { createQuery } from 'mst-query'; 106 | import { MessageModel } from './models'; 107 | import { getItems } from './api'; 108 | 109 | const MessageListQuery = createQuery('MessageListQuery', { 110 | data: types.array(types.reference(MessageModel)), 111 | request: types.model({ filter: '' }), 112 | endpoint({ request }) { 113 | return fetch(`messages?filter=${request.filter}`).then((res) => res.json()); 114 | }, 115 | }); 116 | ``` 117 | 118 | ### `useQuery` 119 | 120 | ```tsx 121 | import { useQuery } from 'mst-query'; 122 | import { observer } from 'mobx-react'; 123 | import { MessageQuery } from './MessageQuery'; 124 | 125 | const MesssageView = observer((props) => { 126 | const { id, snapshot, result } = props; 127 | const rootStore = useRootStore(); 128 | const { 129 | data, 130 | error, 131 | isLoading, 132 | isFetched, 133 | isRefetching, 134 | isFetchingMore, 135 | query, 136 | refetch, 137 | cachedAt, 138 | } = useQuery(rootStore.messageStore.messageQuery, { 139 | data: snapshot, 140 | request: { id }, 141 | enabled: !!id, 142 | onError(data, self) {}, 143 | staleTime: 0, 144 | }); 145 | if (error) { 146 | return
An error occured...
; 147 | } 148 | if (isLoading) { 149 | return
Loading...
; 150 | } 151 | return
{data.message}
; 152 | }); 153 | ``` 154 | 155 | ## Paginated and infinite lists 156 | 157 | ```tsx 158 | import { types } from 'mobx-state-tree'; 159 | import { createInfiniteQuery, RequestModel } from 'mst-query'; 160 | import { MessageModel } from './models'; 161 | 162 | const MessagesQuery = createInfiniteQuery('MessagesQuery', { 163 | data: types.model({ items: types.array(types.reference(MessageModel)) }), 164 | pagination: types.model({ offset: types.number, limit: types.number }), 165 | endpoint({ request }) { 166 | return fetch(`messages?offset=${request.offset}&limit=${request.limit}`).then((res) => 167 | res.json() 168 | ); 169 | }, 170 | }); 171 | 172 | const MessageStore = createModelStore('MessageStore', MessageModel).props({ 173 | messagesQuery: types.optional(MessagesQuery, {}), 174 | }); 175 | ``` 176 | 177 | ```tsx 178 | import { useInfiniteQuery } from 'mst-query'; 179 | import { observer } from 'mobx-react'; 180 | import { MessageListQuery } from './MessageListQuery'; 181 | 182 | const MesssageListView = observer((props) => { 183 | const [offset, setOffset] = useState(0); 184 | const { data, isFetchingMore, query } = useInfiniteQuery(messageStore.messagesQuery, { 185 | request: { filter: '' }, 186 | pagination: { offset, limit: 20 }, 187 | }); 188 | if (isFetchingMore) { 189 | return
Is fetching more results...
; 190 | } 191 | return ( 192 |
193 | {data.items.map((item) => ( 194 | 195 | ))} 196 | 197 |
198 | ); 199 | }); 200 | ``` 201 | 202 | ## Mutations 203 | 204 | ### `createMutation` 205 | 206 | ```tsx 207 | import { types } from 'mobx-state-tree'; 208 | import { createMutation } from 'mst-query'; 209 | 210 | const AddMessageMutation = createMutation('AddMessage', { 211 | data: types.reference(MessageModel), 212 | request: types.model({ message: types.string }), 213 | }); 214 | 215 | const MessageStore = createModelStore('MessageStore', MessageModel) 216 | .props({ 217 | messagesQuery: types.optional(MessagesQuery, {}), 218 | addMessageMutation: types.optional(AddMessageMutation, {}), 219 | }) 220 | .actions((self) => ({ 221 | afterCreate() { 222 | onMutate(self.addMessageMutation, (data) => { 223 | self.messagesQuery.data?.items.push(data); 224 | }); 225 | }, 226 | })); 227 | ``` 228 | 229 | ### `useMutation` 230 | 231 | ```tsx 232 | import { useMutation } from 'mst-query'; 233 | import { observer } from 'mobx-react'; 234 | import { AddMessageMutation } from './AddMessageMutation'; 235 | 236 | const AddMessage = observer((props) => { 237 | const { messageStore } = props; 238 | const [message, setMessage] = useState(''); 239 | const [addMessage, { isLoading }] = useMutation(messageStore.addMessageMutation); 240 | return ( 241 |
242 |