├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── .gitignore
├── README.md
├── docker-compose.yaml
├── hasura
│ ├── config.yaml
│ ├── metadata
│ │ ├── actions.graphql
│ │ ├── actions.yaml
│ │ ├── allow_list.yaml
│ │ ├── api_limits.yaml
│ │ ├── cron_triggers.yaml
│ │ ├── databases
│ │ │ ├── databases.yaml
│ │ │ └── default
│ │ │ │ └── tables
│ │ │ │ ├── public_address.yaml
│ │ │ │ ├── public_order.yaml
│ │ │ │ ├── public_order_product.yaml
│ │ │ │ ├── public_order_status.yaml
│ │ │ │ ├── public_product.yaml
│ │ │ │ ├── public_product_category_enum.yaml
│ │ │ │ ├── public_product_review.yaml
│ │ │ │ ├── public_site_admin.yaml
│ │ │ │ ├── public_user.yaml
│ │ │ │ └── tables.yaml
│ │ ├── graphql_schema_introspection.yaml
│ │ ├── inherited_roles.yaml
│ │ ├── network.yaml
│ │ ├── query_collections.yaml
│ │ ├── remote_schemas.yaml
│ │ ├── rest_endpoints.yaml
│ │ └── version.yaml
│ ├── migrations
│ │ └── default
│ │ │ └── 1646834482402_init
│ │ │ └── up.sql
│ └── seeds
│ │ └── default
│ │ ├── 01_A_user_seeds.sql
│ │ ├── 01_B_user_address_seeds.sql
│ │ ├── 02_product_seeds_sanitized.sql
│ │ ├── 03_A_order_seeds.sql
│ │ ├── 03_B_order_product_seeds.sql
│ │ └── 04_default_user_login_seeds.sql
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── address
│ │ └── index.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── logo.svg
│ ├── order
│ │ └── index.tsx
│ ├── order_product
│ │ └── index.tsx
│ ├── product
│ │ └── index.tsx
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ ├── setupTests.ts
│ └── user
│ │ └── index.tsx
└── tsconfig.json
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── buildGqlQuery
│ ├── buildArgs.ts
│ ├── buildFields.ts
│ └── index.ts
├── buildQuery
│ └── index.ts
├── buildVariables
│ ├── buildCreateVariables.ts
│ ├── buildGetListVariables.ts
│ ├── buildUpdateVariables.ts
│ ├── index.ts
│ ├── makeNestedTarget.ts
│ └── typeAwareKeyValueReducer.ts
├── customDataProvider
│ └── index.ts
├── getResponseParser
│ ├── index.ts
│ ├── sanitizeResource.test.ts
│ └── sanitizeResource.ts
├── graphql-ast-types-browser
│ ├── definitions
│ │ ├── graphql.js
│ │ ├── index.js
│ │ └── init.js
│ ├── index.d.ts
│ └── index.js
├── helpers
│ ├── fetchActions.ts
│ ├── getArgType.ts
│ ├── getFinalType.test.ts
│ ├── getFinalType.ts
│ ├── isList.test.ts
│ ├── isList.ts
│ ├── isRequired.test.ts
│ └── isRequired.ts
├── index.ts
└── types.ts
└── tsconfig.json
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on:
4 | pull_request:
5 | branches: [master]
6 |
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Install dependencies
13 | run: npm install
14 | - name: Run tests
15 | run: npm run test
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.org
3 | lib
4 | dist
5 | .vscode
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx pretty-quick --staged
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | webpack.config.js
3 | .prettierrc
4 | *.org
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.7.1 (March 6, 2025)
4 |
5 | - Fix a tree shaking bug, thanks to @Rhodanthe1116
6 |
7 | ## 0.7.0 (July 19, 2024)
8 |
9 | - Support react-admin v5, thanks to @quentin-decre
10 |
11 | ## 0.6.1 (April 16, 2024)
12 |
13 | - Add update by pk feature, thanks to @franzwilhelm
14 |
15 | ## 0.6.0 (May 29, 2023)
16 |
17 | **Breaking change**
18 |
19 | - Preserve array structure with array transformation, thanks to @nihauc12
20 |
21 | ## 0.5.6 (May 29, 2023)
22 |
23 | - Feature: Support Disabling Pagination, thanks to @alioguzhan
24 | - Feature: Support for JSONB filtering, thanks to @jbek7
25 |
26 | ## 0.5.5 (December 21, 2022)
27 |
28 | - Feature: Hasura raw query nested filtering support, thanks to @n3n
29 | - Feature: Ability to support sorting nulls_first and nulls_last, thanks to @n3n
30 | - Update: graphql v16
31 |
32 | ## 0.5.4 (November 22, 2022)
33 |
34 | - Update all dependencies
35 | - Fix: remove `graphql-ast-types-browser` dependency
36 |
37 | ## 0.5.3 (August 16, 2022)
38 |
39 | - Feature: distinct_on support (#124), thanks to @bharatkashyap
40 | - Feature: added support for empty operator in buildGetListVariables (#125), thanks to @ofilipowicz
41 |
42 | ## 0.5.2 (July 27, 2022)
43 |
44 | - Update to GraphQL 16
45 | - Fix: only filter out GraphQL reserved names (#116), thanks to @n3n
46 | - Feature: Support skipping count aggregate on hasura (#120), thanks to @mohammad-bolt
47 |
48 | ## 0.5.1 (June 14, 2022)
49 |
50 | - Fix dependency issue
51 |
52 | ## 0.5.0 (June 6, 2022)
53 |
54 | - Upgrade library and sample to React Admin v4, thanks to @LucaColonnello
55 |
56 | ## 0.4.2 (May 6, 2022)
57 |
58 | - Example: Add Example v3 App
59 | - Feature: \_nin operator (#89), thanks to @fkowal
60 | - Feature: Enable support for \_contains operator and nested path in jsonb joins, thanks to @fkowal
61 | - Feature: Support nested fields when sorting by multiple columns, thanks to @daa
62 | - Bug Fix: buildFields types (#91), thanks to @cpv123
63 | - Bug Fix: Keep null values when sanitizing resources (#97), thanks to @nselikoff
64 |
65 | ## 0.4.1 (April 7, 2022)
66 |
67 | - Bug Fix: Variables for mutations are not being populated, thanks to @nselikoff
68 |
69 | ## 0.4.0 (March 2, 2022)
70 |
71 | - Full Typescript rewrite thanks to Chris Vibert @cpv123
72 |
73 | ## 0.3.0 (March 2, 2022)
74 |
75 | - Bug Fix: Update only includes edited fields
76 | - Feature: Support sorting by multiple fields
77 | - Bug Fix: Return dataProvider object, not function
78 | - Bug fix: nested keys with array values
79 |
80 | ## 0.2.0 (June 30, 2021)
81 |
82 | - Feature: Update only permitted fields.
83 | - Feature: Add option for custom aggregate field names.
84 | - Feature: Reference a nested object in a reference field.
85 | - Bug Fix: Issue in sanitizing a null value in an array.
86 | - Feature: Add support for nested field filtering
87 | - Bug Fix: Fix issue with null / dates
88 | - Bug Fix: Fix error with react-admin 1.13.0 for date inputs
89 |
90 | ## 0.1.0 (January 19, 2021)
91 |
92 | - **Breaking change**: This release is a complete rewrite of the library, replacing the API with code from `ra-data-hasura-graphql` library. The `steams/ra-data-hasura-graphql` will henceforth be archived. Refer `README.md` for usage instructions.
93 |
94 | ## 0.0.8 (March 18, 2020)
95 |
96 | - Bug Fix: Translate id to primary key for custom primary keys.
97 | - Bug Fix: Respect primary key on order by.
98 | - Bug Fix: Fix typo in GET_MANY_REFERENCE.
99 | - Bug Fix: Set `asc` as default sorting order in GET_LIST.
100 |
101 | ## 0.0.7 (September 17, 2019)
102 |
103 | - Bug Fix: Re-build library to fix discrepancies. Pass `where` arguments to `count` query.
104 | - Feature: Add support for httpClient to pass in dynamic headers. Backwards compatibility maintained for static headers.
105 | - Update package dependencies.
106 |
107 | ## 0.0.6 (June 14, 2019)
108 |
109 | - Bug Fix: Fix sort order, fix primary key when response not an array and add filters to GET\_\* operations.
110 |
111 | ## 0.0.5 (May 16, 2019)
112 |
113 | - Feature: Support specifying primary keys other than id for tables using a config object
114 | Example: `const config = { 'primaryKey': {'author': 'name'} }`
115 |
116 | ## 0.0.4 (April 25, 2019)
117 |
118 | - Feature: Support multiple schemas using "." separator.
119 | Example: ``
120 |
121 | ## 0.0.3 (January 24, 2019)
122 |
123 | - Bug Fix: Fix count query to support UUID
124 |
125 | ## 0.0.2 (January 20, 2019)
126 |
127 | - Bug Fix: GET_MANY_REFERENCE definition
128 |
129 | ## 0.0.1 (January 19, 2019)
130 |
131 | - Add support for hasura data provider
132 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Radcliffe Robinson
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ra-data-hasura
2 |
3 | A GraphQL data provider for [react-admin v4](https://marmelab.com/react-admin) tailored to target [Hasura](https://hasura.io/) GraphQL endpoints. For React Admin v3 use v0.4.2 of this library. For React Admin v5 support, use >v0.7.0 of this library.
4 |
5 | - [ra-data-hasura](#ra-data-hasura)
6 | - [Benefits and Motivation](#benefits-and-motivation)
7 | - [Installation](#installation)
8 | - [Usage](#usage)
9 | - [How It Works](#how-it-works)
10 | - [Options](#options)
11 | - [Customize the Apollo client](#customize-the-apollo-client)
12 | - [Adding Authentication Headers](#adding-authentication-headers)
13 | - [Customize the introspection](#customize-the-introspection)
14 | - [Customize the Data Return](#customize-the-data-return)
15 | - [Customizing queries](#customizing-queries)
16 | - [Example: extending a query to include related entities](#example-extending-a-query-to-include-related-entities)
17 | - [Example: write a completely custom query](#example-write-a-completely-custom-query)
18 | - [Special Filter Feature](#special-filter-feature)
19 | - [Nested filtering](#nested-filtering)
20 | - [Jsonb filtering](#jsonb-filtering)
21 | - [Sorting lists by multiple columns](#sorting-lists-by-multiple-columns)
22 | - [Contributing](#contributing)
23 | - [Credits](#credits)
24 |
25 | Example applications demonstrating usage:
26 |
27 | - [react-admin-low-code](https://github.com/cpursley/react-admin-low-code) (basic usage)
28 | - [react-admin-hasura-queries](https://github.com/cpv123/react-admin-hasura-queries) (usage with custom queries)
29 |
30 | ## Benefits and Motivation
31 |
32 | This utility is built on top of [ra-data-graphql](https://github.com/vladimiregorov/react-admin/blob/master/packages/ra-data-graphql/README.md) and is a custom data provider for the current Hasura GraphQL API format.
33 |
34 | The existing ra-data-graphql-simple provider, requires that your GraphQL endpoint implement a specific grammar for the objects and methods exposed, which is different with Hasura because the exposed objects and methods are generated differently.
35 |
36 | This utility auto generates valid GraphQL queries based on the properties exposed by the Hasura API such as `object_bool_exp` and `object_set_input`.
37 |
38 | ## Installation
39 |
40 | Install with:
41 |
42 | ```sh
43 | npm install --save graphql ra-data-hasura
44 | ```
45 |
46 | ## Usage
47 |
48 | The `ra-data-hasura` package exposes a single function with the following signature:
49 |
50 | ```js
51 | buildHasuraProvider(
52 | options?: Object,
53 | buildGqlQueryOverrides?: Object,
54 | customBuildVariables?: Function,
55 | customGetResponseParser?: Function,
56 | ) => Function
57 | ```
58 |
59 | See the [Options](#options) and [Customizing queries](#customizing-queries) sections below for more details on these arguments.
60 |
61 | This function acts as a constructor for a `dataProvider` based on a Hasura GraphQL endpoint. When executed, this function calls the endpoint, running an [introspection](http://graphql.org/learn/introspection/) query to learn about the specific data models exposed by your Hasura endpoint. It uses the result of this query (the GraphQL schema) to automatically configure the `dataProvider` accordingly.
62 |
63 | ```jsx
64 | // Initialize the dataProvider before rendering react-admin resources.
65 | import React, { useState, useEffect } from 'react';
66 | import buildHasuraProvider from 'ra-data-hasura';
67 | import { Admin, Resource } from 'react-admin';
68 |
69 | import { PostCreate, PostEdit, PostList } from './posts';
70 |
71 | const App = () => {
72 | const [dataProvider, setDataProvider] = useState(null);
73 |
74 | useEffect(() => {
75 | const buildDataProvider = async () => {
76 | const dataProvider = await buildHasuraProvider({
77 | clientOptions: { uri: 'http://localhost:8080/v1/graphql' },
78 | });
79 | setDataProvider(() => dataProvider);
80 | };
81 | buildDataProvider();
82 | }, []);
83 |
84 | if (!dataProvider) return
Loading...
;
85 |
86 | return (
87 |
88 |
94 |
95 | );
96 | };
97 |
98 | export default App;
99 | ```
100 |
101 | ## How It Works
102 |
103 | The data provider converts React Admin queries into the form expected by Hasura's GraphQL API. For example, a React Admin `GET_LIST` request for a person resource with the parameters :
104 |
105 | ```json
106 | {
107 | "pagination": { "page": 1, "perPage": 5 },
108 | "sort": { "field": "name", "order": "DESC" },
109 | "filter": {
110 | "ids": [101, 102]
111 | }
112 | }
113 | ```
114 |
115 | will generate the following GraphQL request for Hasura :
116 |
117 | ```
118 | query person($limit: Int, $offset: Int, $order_by: [person_order_by!]!, $where: person_bool_exp) {
119 | items: person(limit: $limit, offset: $offset, order_by: $order_by, where: $where) {
120 | id
121 | name
122 | address_id
123 | }
124 | total: person_aggregate(limit: $limit, offset: $offset, order_by: $order_by, where: $where) {
125 | aggregate {
126 | count
127 | }
128 | }
129 | }
130 | ```
131 |
132 | With the following variables to be passed alongside the query:
133 |
134 | ```
135 | {
136 | limit: 5,
137 | offset: 0,
138 | order_by: { name: 'desc' },
139 | where: {
140 | _and: [
141 | {
142 | id: {
143 | _in: [101, 102]
144 | }
145 | }
146 | ]
147 | }
148 | }
149 |
150 | ```
151 |
152 | React Admin sort and filter objects will be converted appropriately, for example sorting with dot notation:
153 |
154 | ```jsx
155 | export const PostList = (props) => (
156 |
157 | ...
158 |
159 | );
160 | ```
161 |
162 | will generate the following GraphQL query variables:
163 |
164 | ```js
165 | {
166 | limit: 25,
167 | offset: 0,
168 | order_by: { user: { email: 'desc' } }
169 | }
170 | ```
171 |
172 | and
173 |
174 | ```jsx
175 | export const AddressList = () => (
176 |
180 | ...
181 |
182 | );
183 | ```
184 |
185 | will generate the following GraphQL query variables:
186 |
187 | ```json
188 | {
189 | // ...
190 | "order_by": {
191 | "city": "desc"
192 | },
193 | "distinct_on": "city"
194 | }
195 | ```
196 |
197 | Keep in mind that `distinct_on` must be used in conjunction with `order_by`, otherwise a `"distinct_on" columns must match initial "order_by" columns"` error will result. See more [here](https://hasura.io/docs/latest/queries/postgres/distinct-queries/#the-distinct_on-argument).
198 |
199 | ## Options
200 |
201 | ### Customize the Apollo client
202 |
203 | You can either supply just the client options:
204 |
205 | ```js
206 | buildGraphQLProvider({
207 | clientOptions: {
208 | uri: 'http://localhost:8080/v1/graphql',
209 | ...otherApolloOptions,
210 | },
211 | });
212 | ```
213 |
214 | or supply the client instance directly:
215 |
216 | ```js
217 | buildGraphQLProvider({ client: myClient });
218 | ```
219 |
220 | ### Adding Authentication Headers
221 |
222 | To send authentication headers, you'll need to supply the client instance directly with headers defined:
223 |
224 | ```js
225 | import { ApolloClient, InMemoryCache } from '@apollo/client';
226 |
227 | const myClientWithAuth = new ApolloClient({
228 | uri: 'http://localhost:8080/v1/graphql',
229 | cache: new InMemoryCache(),
230 | headers: {
231 | 'x-hasura-admin-secret': 'hasuraAdminSecret',
232 | // 'Authorization': `Bearer xxxx`,
233 | },
234 | });
235 |
236 | buildHasuraProvider({ client: myClientWithAuth });
237 | ```
238 |
239 |
240 |
241 | Adding headers using just client options
242 |
243 | You can also add headers using only client options rather than the client itself:
244 |
245 | ```js
246 | import { createHttpLink } from '@apollo/client';
247 | import { setContext } from '@apollo/client/link/context';
248 |
249 | const authLink = setContext((_, { headers }) => ({
250 | headers: {
251 | ...headers,
252 | 'x-hasura-admin-secret': 'hasuraAdminSecret',
253 | // 'Authorization': `Bearer xxxx`,
254 | },
255 | }));
256 |
257 | const httpLink = createHttpLink({
258 | uri: 'http://localhost:8080/v1/graphql',
259 | });
260 |
261 | const clientOptionsWithAuth = {
262 | link: authLink.concat(httpLink),
263 | };
264 |
265 | buildHasuraProvider({ client: clientOptionsWithAuth });
266 | ```
267 |
268 |
269 |
270 | ### Customize the introspection
271 |
272 | These are the default options for introspection:
273 |
274 | ```js
275 | const introspectionOptions = {
276 | include: [], // Either an array of types to include or a function which will be called for every type discovered through introspection
277 | exclude: [], // Either an array of types to exclude or a function which will be called for every type discovered through introspection
278 | };
279 |
280 | // Including types
281 | const introspectionOptions = {
282 | include: ['Post', 'Comment'],
283 | };
284 |
285 | // Excluding types
286 | const introspectionOptions = {
287 | exclude: ['CommandItem'],
288 | };
289 |
290 | // Including types with a function
291 | const introspectionOptions = {
292 | include: (type) => ['Post', 'Comment'].includes(type.name),
293 | };
294 |
295 | // Including types with a function
296 | const introspectionOptions = {
297 | exclude: (type) => !['Post', 'Comment'].includes(type.name),
298 | };
299 | ```
300 |
301 | **Note**: `exclude` and `include` are mutually exclusives and `include` will take precendance.
302 |
303 | **Note**: When using functions, the `type` argument will be a type returned by the introspection query. Refer to the [introspection](http://graphql.org/learn/introspection/) documentation for more information.
304 |
305 | Pass the introspection options to the `buildApolloProvider` function:
306 |
307 | ```js
308 | buildApolloProvider({ introspection: introspectionOptions });
309 | ```
310 |
311 | ### Customize the Data Return
312 |
313 | Once the data is returned back from the provider, you can customize it by implementing the `DataProvider` interface. [An example is changing the ID key](https://marmelab.com/react-admin/FAQ.html#can-i-have-custom-identifiersprimary-keys-for-my-resources).
314 |
315 | ```typescript
316 | const [dataProvider, setDataProvider] = React.useState(
317 | null
318 | );
319 |
320 | React.useEffect(() => {
321 | const buildDataProvider = async () => {
322 | const dataProviderHasura = await buildHasuraProvider({
323 | clientOptions: {
324 | uri: 'http://localhost:8080/v1/graphql',
325 | },
326 | });
327 | const modifiedProvider: DataProvider = {
328 | getList: async (resource, params) => {
329 | let { data, ...metadata } = await dataProviderHasura.getList(
330 | resource,
331 | params
332 | );
333 |
334 | if (resource === 'example_resource_name') {
335 | data = data.map(
336 | (val): Record => ({
337 | ...val,
338 | id: val.region_id,
339 | })
340 | );
341 | }
342 |
343 | return {
344 | data: data as any[],
345 | ...metadata,
346 | };
347 | },
348 | getOne: (resource, params) => dataProviderHasura.getOne(resource, params),
349 | getMany: (resource, params) =>
350 | dataProviderHasura.getMany(resource, params),
351 | getManyReference: (resource, params) =>
352 | dataProviderHasura.getManyReference(resource, params),
353 | update: (resource, params) => dataProviderHasura.update(resource, params),
354 | updateMany: (resource, params) =>
355 | dataProviderHasura.updateMany(resource, params),
356 | create: (resource, params) => dataProviderHasura.create(resource, params),
357 | delete: (resource, params) => dataProviderHasura.delete(resource, params),
358 | deleteMany: (resource, params) =>
359 | dataProviderHasura.deleteMany(resource, params),
360 | };
361 | setDataProvider(() => modifiedProvider);
362 | };
363 | buildDataProvider();
364 | }, []);
365 | ```
366 |
367 | ## Customizing queries
368 |
369 | Queries built by this data provider are made up of 3 parts:
370 |
371 | 1. The set of fields requested
372 | 2. The variables defining the query constraints like `where, order_by, limit, offset`
373 | 3. The response format e.g. `{ data: {...}, total: 100 }`
374 |
375 | Each of these can be customized - functions overriding numbers 2 and 3 can be passed to directly to `buildDataProvider` as shown in [Usage](#usage), whilst number 1 can be customized in parts using the `buildGqlQueryOverrides` object argument:
376 |
377 | ```js
378 | {
379 | buildFields?: Function,
380 | buildMetaArgs?: Function,
381 | buildArgs?: Function,
382 | buildApolloArgs?: Function,
383 | }
384 | ```
385 |
386 | A likely scenario is that you want to override only the `buildFields` part so that you can customize your GraphQL queries - requesting fewer fields, more fields, nested fields etc.
387 |
388 | This can be easily done, and importantly can be done using `gql` template literal tags, as shown in the examples below. Take a look at this [demo application](https://github.com/cpv123/react-admin-hasura-queries) to see it in action.
389 |
390 | ### Example: extending a query to include related entities
391 |
392 | By default, the data provider will generate queries that include all fields on a resource, but without any relationships to nested entities. If you would like to keep these base fields but extend the query to also include related entities, then you can write a custom `buildFields` like this:
393 |
394 | ```ts
395 | import buildDataProvider, { buildFields } from 'ra-data-hasura';
396 | import type { BuildFields } from 'ra-data-hasura';
397 | import gql from 'graphql-tag';
398 |
399 | /**
400 | * Extracts just the fields from a GraphQL AST.
401 | * @param {GraphQL AST} queryAst
402 | */
403 | const extractFieldsFromQuery = (queryAst) => {
404 | return queryAst.definitions[0].selectionSet.selections;
405 | };
406 |
407 | // Define the additional fields that we want.
408 | const EXTENDED_GET_ONE_USER = gql`
409 | {
410 | todos_aggregate {
411 | aggregate {
412 | count
413 | }
414 | }
415 | }
416 | `;
417 |
418 | const customBuildFields: BuildFields = (type, fetchType) => {
419 | const resourceName = type.name;
420 |
421 | // First take the default fields (all, but no related or nested).
422 | const defaultFields = buildFields(type, fetchType);
423 |
424 | if (resourceName === 'users' && fetchType === 'GET_ONE') {
425 | const relatedEntities = extractFieldsFromQuery(EXTENDED_GET_ONE_USER);
426 | defaultFields.push(...relatedEntities);
427 | }
428 |
429 | // Extend other queries for other resources/fetchTypes here...
430 |
431 | return defaultFields;
432 | };
433 |
434 | buildDataProvider(options, { buildFields: customBuildFields });
435 | ```
436 |
437 | ### Example: write a completely custom query
438 |
439 | If you want full control over the GraphQL query, then you can define the entire set of fields like this:
440 |
441 | ```ts
442 | import gql from 'graphql-tag';
443 | import buildDataProvider, { buildFields } from 'ra-data-hasura';
444 | import type { BuildFields } from 'ra-data-hasura';
445 |
446 | /**
447 | * Extracts just the fields from a GraphQL AST.
448 | * @param {GraphQL AST} queryAst
449 | */
450 | const extractFieldsFromQuery = (queryAst) => {
451 | return queryAst.definitions[0].selectionSet.selections;
452 | };
453 |
454 | const GET_ONE_USER = gql`
455 | {
456 | id
457 | name
458 | todos(
459 | where: { is_completed: { _eq: false } }
460 | order_by: { created_at: asc }
461 | ) {
462 | title
463 | }
464 | todos_aggregate {
465 | aggregate {
466 | count
467 | }
468 | }
469 | }
470 | `;
471 |
472 | const customBuildFields: BuildFields = (type, fetchType) => {
473 | const resourceName = type.name;
474 |
475 | if (resourceName === 'users' && fetchType === 'GET_ONE') {
476 | return extractFieldsFromQuery(GET_ONE_USER);
477 | }
478 |
479 | // No custom query defined, so use the default query fields (all, but no related/nested).
480 | return buildFields(type, fetchType);
481 | };
482 |
483 | buildDataProvider(options, { buildFields: customBuildFields });
484 | ```
485 |
486 | Note that when using this approach in particular, it is possible that you will come across [this issue](https://github.com/cpv123/react-admin-hasura-queries#troubleshooting).
487 |
488 | ## Special Filter Feature
489 |
490 | This adapter allows filtering several columns at a time with using specific comparators, e.g. `ilike`, `like`, `eq`, etc.
491 |
492 | ```tsx
493 |
494 |
499 |
500 | ```
501 |
502 | It will generate the following filter payload
503 |
504 | ```json
505 | {
506 | "variables": {
507 | "where": {
508 | "_and": [],
509 | "_or": [
510 | {
511 | "email": {
512 | "_ilike": "%edu%"
513 | }
514 | },
515 | {
516 | "first_name": {
517 | "_eq": "edu"
518 | }
519 | },
520 | {
521 | "last_name": {
522 | "_like": "%edu%"
523 | }
524 | }
525 | ]
526 | },
527 | "limit": 10,
528 | "offset": 0,
529 | "order_by": {
530 | "id": "asc"
531 | }
532 | }
533 | }
534 | ```
535 |
536 | The adapter assigns default comparator depends on the data type if it is not provided.
537 | For string data types, it assumes as text search and uses `ilike` otherwise it uses `eq`.
538 | For string data types that uses `like` or `ilike` it automatically transform the filter `value` as `%value%`.
539 |
540 | ### Nested filtering
541 |
542 | Nested filtering is supported using # as a field separator.
543 |
544 | ```tsx
545 |
550 | ```
551 |
552 | Will produce the following payload:
553 |
554 | ```json
555 | {
556 | "where": {
557 | "_and": [],
558 | "_or": [
559 | {
560 | "indication": {
561 | "name": {
562 | "_ilike": "%TEXT%"
563 | }
564 | }
565 | },
566 | {
567 | "drug": {
568 | "name": {
569 | "_ilike": "%TEXT%"
570 | }
571 | }
572 | },
573 | {
574 | "sponsor": {
575 | "name": {
576 | "_ilike": "%TEXT%"
577 | }
578 | }
579 | }
580 | ]
581 | },
582 | "limit": 10,
583 | "offset": 0,
584 | "order_by": {
585 | "id": "asc"
586 | }
587 | }
588 | ```
589 |
590 | ## Jsonb filtering
591 |
592 | ```jsx
593 |
594 | ```
595 |
596 | Will produce payload:
597 |
598 | ```json
599 | {
600 | "where": {
601 | "_and": [
602 | {
603 | "users": {
604 | "preferences": {
605 | "_contains": {
606 | "ux": {
607 | "theme": "%TEXT"
608 | }
609 | }
610 | }
611 | }
612 | }
613 | ]
614 | },
615 | "limit": 10,
616 | "offset": 0,
617 | "order_by": {
618 | "id": "asc"
619 | }
620 | }
621 | ```
622 |
623 | Fetch data matching a jsonb `_contains` operation
624 |
625 | ```jsx
626 |
632 |
633 | ...
634 |
635 |
636 | } />
637 | ```
638 |
639 | Will produce payload:
640 |
641 | ```json
642 | {
643 | "where": {
644 | "_and": [
645 | {
646 | "payments": {
647 | "details": {
648 | "_contains": {
649 | "processor": {
650 | "%{rec.processor}_id": "%{rec.id}"
651 | }
652 | }
653 | }
654 | }
655 | }
656 | ]
657 | }
658 | }
659 | ```
660 |
661 | ## Sorting lists by multiple columns
662 |
663 | Hasura support [sorting by multiple fields](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/sorting.html#sorting-by-multiple-fields) but React Admin itself doesn't allow the `List` component to receive an array as the `sort` prop. So to achieve sorting by multiple fields, separate the field and order values using a comma.
664 |
665 | For example, a list like
666 |
667 | ```jsx
668 | const TodoList = (props) => (
669 |
670 | ...
671 |
672 | );
673 | ```
674 |
675 | will generate a query with an `order_by` variable like
676 |
677 | ```
678 | order_by: [{ title: "asc" }, { is_completed: "desc" }]
679 | ```
680 |
681 | Fields may contain dots to specify sorting by nested object properties similarly to React Admin `source` property.
682 |
683 | ## Contributing
684 |
685 | To modify, extend and test this package locally,
686 |
687 | ```
688 | $ cd ra-data-hasura
689 | $ npm link
690 | ```
691 |
692 | Now use this local package in your react app for testing
693 |
694 | ```
695 | $ cd my-react-app
696 | $ npm link ra-data-hasura
697 | ```
698 |
699 | Build the library by running `npm run build` and it will generate the transpiled version of the library under `lib` folder.
700 |
701 | ## Credits
702 |
703 | We would like to thank [Steams](https://github.com/Steams) and all the contributors to this library for porting this adapter to support GraphQL spec, since all the releases till v0.0.8 were based off the REST API spec.
704 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # ra-data-hasura Example
2 |
3 | A sample React Admin app using ra-data-hasura and the [Hasura Super App schema](https://github.com/hasura/hasura-ecommerce).
4 |
5 | In the future, login functionality will be added. Meanwhile, a JWT representing a site-admin role has been pre-generated.
6 |
7 | You should be able to view, edit, and delete different schema items as well as see their relationships.
8 |
9 | To run:
10 |
11 | ```bash
12 | docker compose up -d
13 |
14 | hasura seed apply --database-name default --project hasura
15 |
16 | npm i
17 |
18 | npm run start
19 | ```
20 |
21 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
22 |
--------------------------------------------------------------------------------
/example/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | postgres:
3 | image: postgres:15
4 | restart: always
5 | volumes:
6 | - db_data:/var/lib/postgresql/data
7 | ports:
8 | # Expose the port for tooling (SQL language server in IDE, connecting with GUI's etc)
9 | - 5432:5432
10 | environment:
11 | POSTGRES_PASSWORD: postgrespassword
12 |
13 | graphql-engine:
14 | image: hasura/graphql-engine:v2.15.2.cli-migrations-v3
15 | volumes:
16 | - ./hasura/migrations:/hasura-migrations
17 | - ./hasura/metadata:/hasura-metadata
18 | ports:
19 | - 8080:8080
20 | depends_on:
21 | - 'postgres'
22 | restart: always
23 | environment:
24 | NEXTJS_SERVER_URL: http://nextjs:3000
25 | HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
26 | PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
27 | ## enable the console served by server
28 | HASURA_GRAPHQL_ENABLE_CONSOLE: 'true' # set to "false" to disable console
29 | ## enable debugging mode. It is recommended to disable this in production
30 | HASURA_GRAPHQL_DEV_MODE: 'true'
31 | HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
32 | HASURA_GRAPHQL_ADMIN_SECRET: my-secret
33 | HASURA_GRAPHQL_JWT_SECRET: '{ "type": "HS256", "key": "this-is-a-generic-HS256-secret-key-and-you-should-really-change-it" }'
34 | HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous
35 |
36 | volumes:
37 | db_data:
38 |
--------------------------------------------------------------------------------
/example/hasura/config.yaml:
--------------------------------------------------------------------------------
1 | version: 3
2 | endpoint: http://localhost:8080
3 | admin_secret: my-secret
4 | metadata_directory: metadata
5 | actions:
6 | kind: synchronous
7 | handler_webhook_baseurl: http://localhost:3000
8 |
--------------------------------------------------------------------------------
/example/hasura/metadata/actions.graphql:
--------------------------------------------------------------------------------
1 | type Query {
2 | adminLogin(params: AdminLoginInput!): JWT
3 | }
4 |
5 | type Mutation {
6 | adminSignup(params: AdminSignupInput!): JWT
7 | }
8 |
9 | type Mutation {
10 | createPaymentIntent(
11 | params: CreatePaymentIntentInput!
12 | ): PaymentIntentClientSecret
13 | }
14 |
15 | type Mutation {
16 | login(params: LoginInput!): JWT
17 | }
18 |
19 | type Query {
20 | refreshToken(params: RefreshTokenInput!): RefreshTokenJWT
21 | }
22 |
23 | type Mutation {
24 | signup(params: SignupInput!): JWT
25 | }
26 |
27 | input SignupInput {
28 | name: String!
29 | email: String!
30 | password: String!
31 | }
32 |
33 | input LoginInput {
34 | email: String!
35 | password: String!
36 | }
37 |
38 | input AdminLoginInput {
39 | email: String!
40 | password: String!
41 | }
42 |
43 | input AdminSignupInput {
44 | name: String!
45 | email: String!
46 | password: String!
47 | }
48 |
49 | input CreatePaymentIntentInput {
50 | paymentAmount: Float!
51 | }
52 |
53 | input RefreshTokenInput {
54 | refreshToken: String!
55 | }
56 |
57 | type PaymentIntentClientSecret {
58 | clientSecret: String!
59 | }
60 |
61 | type JWT {
62 | name: String!
63 | email: String!
64 | token: String!
65 | refreshToken: String!
66 | }
67 |
68 | type RefreshTokenJWT {
69 | token: String!
70 | }
71 |
--------------------------------------------------------------------------------
/example/hasura/metadata/actions.yaml:
--------------------------------------------------------------------------------
1 | actions:
2 | - name: adminLogin
3 | definition:
4 | kind: ''
5 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/admin-login'
6 | permissions:
7 | - role: anonymous
8 | - name: adminSignup
9 | definition:
10 | kind: synchronous
11 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/admin-signup'
12 | permissions:
13 | - role: site-admin
14 | - name: createPaymentIntent
15 | definition:
16 | kind: synchronous
17 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/create-payment-intent'
18 | permissions:
19 | - role: anonymous
20 | - role: site-admin
21 | - role: user
22 | - name: login
23 | definition:
24 | kind: synchronous
25 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/login'
26 | permissions:
27 | - role: anonymous
28 | - name: refreshToken
29 | definition:
30 | kind: ''
31 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/refresh-token'
32 | permissions:
33 | - role: anonymous
34 | - role: site-admin
35 | - role: user
36 | - name: signup
37 | definition:
38 | kind: synchronous
39 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/signup'
40 | permissions:
41 | - role: anonymous
42 | custom_types:
43 | enums: []
44 | input_objects:
45 | - name: SignupInput
46 | - name: LoginInput
47 | - name: AdminLoginInput
48 | - name: AdminSignupInput
49 | - name: CreatePaymentIntentInput
50 | - name: RefreshTokenInput
51 | objects:
52 | - name: PaymentIntentClientSecret
53 | - name: JWT
54 | - name: RefreshTokenJWT
55 | scalars: []
56 |
--------------------------------------------------------------------------------
/example/hasura/metadata/allow_list.yaml:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/api_limits.yaml:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/cron_triggers.yaml:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/databases.yaml:
--------------------------------------------------------------------------------
1 | - name: default
2 | kind: postgres
3 | configuration:
4 | connection_info:
5 | use_prepared_statements: false
6 | database_url:
7 | from_env: PG_DATABASE_URL
8 | isolation_level: read-committed
9 | tables: '!include default/tables/tables.yaml'
10 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_address.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: address
4 | object_relationships:
5 | - name: user
6 | using:
7 | foreign_key_constraint_on: user_id
8 | array_relationships:
9 | - name: orders_with_billing_address
10 | using:
11 | foreign_key_constraint_on:
12 | column: billing_address_id
13 | table:
14 | schema: public
15 | name: order
16 | - name: orders_with_shipping_address
17 | using:
18 | foreign_key_constraint_on:
19 | column: shipping_address_id
20 | table:
21 | schema: public
22 | name: order
23 | insert_permissions:
24 | - role: site-admin
25 | permission:
26 | check: {}
27 | columns: '*'
28 | - role: user
29 | permission:
30 | check:
31 | user_id:
32 | _eq: X-Hasura-User-Id
33 | columns: '*'
34 | select_permissions:
35 | - role: site-admin
36 | permission:
37 | columns: '*'
38 | filter: {}
39 | allow_aggregations: true
40 | - role: user
41 | permission:
42 | columns: '*'
43 | filter:
44 | user_id:
45 | _eq: X-Hasura-User-Id
46 | update_permissions:
47 | - role: site-admin
48 | permission:
49 | columns: '*'
50 | filter: {}
51 | check: null
52 | - role: user
53 | permission:
54 | columns: '*'
55 | filter:
56 | user_id:
57 | _eq: X-Hasura-User-Id
58 | check: null
59 | delete_permissions:
60 | - role: site-admin
61 | permission:
62 | filter: {}
63 | - role: user
64 | permission:
65 | filter:
66 | user_id:
67 | _eq: X-Hasura-User-Id
68 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_order.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: order
4 | object_relationships:
5 | - name: billing_address
6 | using:
7 | foreign_key_constraint_on: billing_address_id
8 | - name: order_status
9 | using:
10 | foreign_key_constraint_on: status
11 | - name: shipping_address
12 | using:
13 | foreign_key_constraint_on: shipping_address_id
14 | - name: user
15 | using:
16 | foreign_key_constraint_on: user_id
17 | array_relationships:
18 | - name: products
19 | using:
20 | foreign_key_constraint_on:
21 | column: order_id
22 | table:
23 | schema: public
24 | name: order_product
25 | insert_permissions:
26 | - role: site-admin
27 | permission:
28 | check: {}
29 | columns: '*'
30 | - role: user
31 | permission:
32 | check:
33 | user_id:
34 | _eq: X-Hasura-User-Id
35 | columns: '*'
36 | select_permissions:
37 | - role: site-admin
38 | permission:
39 | columns: '*'
40 | filter: {}
41 | allow_aggregations: true
42 | - role: user
43 | permission:
44 | columns: '*'
45 | filter:
46 | user_id:
47 | _eq: X-Hasura-User-Id
48 | update_permissions:
49 | - role: site-admin
50 | permission:
51 | columns: '*'
52 | filter: {}
53 | check: null
54 | delete_permissions:
55 | - role: site-admin
56 | permission:
57 | filter: {}
58 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_order_product.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: order_product
4 | object_relationships:
5 | - name: order
6 | using:
7 | foreign_key_constraint_on: order_id
8 | - name: product
9 | using:
10 | foreign_key_constraint_on: product_id
11 | insert_permissions:
12 | - role: site-admin
13 | permission:
14 | check: {}
15 | columns: '*'
16 | - role: user
17 | permission:
18 | check:
19 | order:
20 | user_id:
21 | _eq: X-Hasura-User-Id
22 | columns: '*'
23 | select_permissions:
24 | - role: site-admin
25 | permission:
26 | columns: '*'
27 | filter: {}
28 | allow_aggregations: true
29 | - role: user
30 | permission:
31 | columns: '*'
32 | filter:
33 | order:
34 | user_id:
35 | _eq: X-Hasura-User-Id
36 | update_permissions:
37 | - role: site-admin
38 | permission:
39 | columns: '*'
40 | filter: {}
41 | check: null
42 | delete_permissions:
43 | - role: site-admin
44 | permission:
45 | filter: {}
46 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_order_status.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: order_status
4 | is_enum: true
5 | array_relationships:
6 | - name: orders
7 | using:
8 | foreign_key_constraint_on:
9 | column: status
10 | table:
11 | schema: public
12 | name: order
13 | insert_permissions:
14 | - role: site-admin
15 | permission:
16 | check: {}
17 | columns: '*'
18 | select_permissions:
19 | - role: site-admin
20 | permission:
21 | columns: '*'
22 | filter: {}
23 | - role: user
24 | permission:
25 | columns: '*'
26 | filter: {}
27 | update_permissions:
28 | - role: site-admin
29 | permission:
30 | columns: '*'
31 | filter: {}
32 | check: null
33 | delete_permissions:
34 | - role: site-admin
35 | permission:
36 | filter: {}
37 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_product.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: product
4 | object_relationships:
5 | - name: category
6 | using:
7 | foreign_key_constraint_on: category_display_name
8 | array_relationships:
9 | - name: orders
10 | using:
11 | foreign_key_constraint_on:
12 | column: product_id
13 | table:
14 | schema: public
15 | name: order_product
16 | - name: product_reviews
17 | using:
18 | foreign_key_constraint_on:
19 | column: product_id
20 | table:
21 | schema: public
22 | name: product_review
23 | insert_permissions:
24 | - role: site-admin
25 | permission:
26 | check: {}
27 | columns: '*'
28 | select_permissions:
29 | - role: anonymous
30 | permission:
31 | columns: '*'
32 | filter: {}
33 | allow_aggregations: true
34 | - role: site-admin
35 | permission:
36 | columns: '*'
37 | filter: {}
38 | allow_aggregations: true
39 | - role: user
40 | permission:
41 | columns: '*'
42 | filter: {}
43 | update_permissions:
44 | - role: site-admin
45 | permission:
46 | columns: '*'
47 | filter: {}
48 | check: null
49 | delete_permissions:
50 | - role: site-admin
51 | permission:
52 | filter: {}
53 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_product_category_enum.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: product_category_enum
4 | is_enum: true
5 | array_relationships:
6 | - name: products
7 | using:
8 | foreign_key_constraint_on:
9 | column: category_display_name
10 | table:
11 | schema: public
12 | name: product
13 | insert_permissions:
14 | - role: site-admin
15 | permission:
16 | check: {}
17 | columns: '*'
18 | select_permissions:
19 | - role: anonymous
20 | permission:
21 | columns: '*'
22 | filter: {}
23 | - role: site-admin
24 | permission:
25 | columns: '*'
26 | filter: {}
27 | - role: user
28 | permission:
29 | columns: '*'
30 | filter: {}
31 | update_permissions:
32 | - role: site-admin
33 | permission:
34 | columns: '*'
35 | filter: {}
36 | check: null
37 | delete_permissions:
38 | - role: site-admin
39 | permission:
40 | filter: {}
41 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_product_review.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: product_review
4 | object_relationships:
5 | - name: product
6 | using:
7 | foreign_key_constraint_on: product_id
8 | - name: user
9 | using:
10 | foreign_key_constraint_on: user_id
11 | insert_permissions:
12 | - role: site-admin
13 | permission:
14 | check: {}
15 | columns: '*'
16 | - role: user
17 | permission:
18 | check:
19 | user:
20 | id:
21 | _eq: X-Hasura-User-Id
22 | orders:
23 | products:
24 | id:
25 | _ceq: product_id
26 | set:
27 | user_id: X-Hasura-User-Id
28 | columns:
29 | - product_id
30 | - rating
31 | - comment
32 | select_permissions:
33 | - role: anonymous
34 | permission:
35 | columns: '*'
36 | filter: {}
37 | - role: site-admin
38 | permission:
39 | columns: '*'
40 | filter: {}
41 | allow_aggregations: true
42 | - role: user
43 | permission:
44 | columns: '*'
45 | filter: {}
46 | update_permissions:
47 | - role: site-admin
48 | permission:
49 | columns: '*'
50 | filter: {}
51 | check: null
52 | - role: user
53 | permission:
54 | columns:
55 | - product_id
56 | - rating
57 | - comment
58 | filter:
59 | user:
60 | id:
61 | _eq: X-Hasura-User-Id
62 | orders:
63 | products:
64 | id:
65 | _ceq: product_id
66 | check: null
67 | set:
68 | user_id: X-Hasura-User-Id
69 | delete_permissions:
70 | - role: site-admin
71 | permission:
72 | filter: {}
73 | - role: user
74 | permission:
75 | filter:
76 | user:
77 | id:
78 | _eq: X-Hasura-User-Id
79 | orders:
80 | products:
81 | id:
82 | _ceq: product_id
83 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_site_admin.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: site_admin
4 | insert_permissions:
5 | - role: site-admin
6 | permission:
7 | check: {}
8 | columns: '*'
9 | select_permissions:
10 | - role: site-admin
11 | permission:
12 | columns: '*'
13 | filter: {}
14 | update_permissions:
15 | - role: site-admin
16 | permission:
17 | columns: '*'
18 | filter: {}
19 | check: null
20 | delete_permissions:
21 | - role: site-admin
22 | permission:
23 | filter: {}
24 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/public_user.yaml:
--------------------------------------------------------------------------------
1 | table:
2 | schema: public
3 | name: user
4 | array_relationships:
5 | - name: addresses
6 | using:
7 | foreign_key_constraint_on:
8 | column: user_id
9 | table:
10 | schema: public
11 | name: address
12 | - name: orders
13 | using:
14 | foreign_key_constraint_on:
15 | column: user_id
16 | table:
17 | schema: public
18 | name: order
19 | - name: product_reviews
20 | using:
21 | foreign_key_constraint_on:
22 | column: user_id
23 | table:
24 | schema: public
25 | name: product_review
26 | insert_permissions:
27 | - role: site-admin
28 | permission:
29 | check: {}
30 | columns: '*'
31 | select_permissions:
32 | - role: site-admin
33 | permission:
34 | columns: '*'
35 | filter: {}
36 | allow_aggregations: true
37 | - role: user
38 | permission:
39 | columns: '*'
40 | filter:
41 | id:
42 | _eq: X-Hasura-User-Id
43 | update_permissions:
44 | - role: site-admin
45 | permission:
46 | columns: '*'
47 | filter: {}
48 | check: null
49 | delete_permissions:
50 | - role: site-admin
51 | permission:
52 | filter: {}
53 |
--------------------------------------------------------------------------------
/example/hasura/metadata/databases/default/tables/tables.yaml:
--------------------------------------------------------------------------------
1 | - '!include public_address.yaml'
2 | - '!include public_order.yaml'
3 | - '!include public_order_product.yaml'
4 | - '!include public_order_status.yaml'
5 | - '!include public_product.yaml'
6 | - '!include public_product_category_enum.yaml'
7 | - '!include public_product_review.yaml'
8 | - '!include public_site_admin.yaml'
9 | - '!include public_user.yaml'
10 |
--------------------------------------------------------------------------------
/example/hasura/metadata/graphql_schema_introspection.yaml:
--------------------------------------------------------------------------------
1 | disabled_for_roles: []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/inherited_roles.yaml:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/network.yaml:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/query_collections.yaml:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/remote_schemas.yaml:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/rest_endpoints.yaml:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/example/hasura/metadata/version.yaml:
--------------------------------------------------------------------------------
1 | version: 3
2 |
--------------------------------------------------------------------------------
/example/hasura/migrations/default/1646834482402_init/up.sql:
--------------------------------------------------------------------------------
1 | CREATE FUNCTION public.set_current_timestamp_updated_at ()
2 | RETURNS TRIGGER
3 | LANGUAGE plpgsql
4 | AS $$
5 | DECLARE
6 | _new record;
7 | BEGIN
8 | _new := NEW;
9 | _new. "updated_at" = NOW();
10 | RETURN _new;
11 | END;
12 | $$;
13 |
14 | -- Create an "url" Postgres type that is an alias for "text"
15 | -- Which validates the input is an URL
16 | CREATE DOMAIN url AS text CHECK (VALUE ~ 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#()?&//=]*)');
17 |
18 | COMMENT ON DOMAIN url IS 'Match URLs (http or https)';
19 |
20 |
21 | CREATE TABLE "address" (
22 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
23 | name text,
24 | created_at timestamptz DEFAULT now() NOT NULL,
25 | updated_at timestamptz DEFAULT now() NOT NULL,
26 | user_id int NOT NULL,
27 | city text NOT NULL,
28 | state text NOT NULL,
29 | -- Use text for zipcode to handle ZIP+4 extended zipcodes
30 | zipcode text NOT NULL,
31 | address_line_one text NOT NULL,
32 | address_line_two text
33 | );
34 |
35 | COMMENT ON TABLE address IS 'A physical billing/shipping address, attached to a user account';
36 |
37 |
38 | CREATE TABLE "site_admin" (
39 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
40 | created_at timestamptz DEFAULT now() NOT NULL,
41 | updated_at timestamptz DEFAULT now() NOT NULL,
42 | name text NOT NULL,
43 | email text NOT NULL UNIQUE,
44 | password text NOT NULL,
45 | refresh_token text UNIQUE
46 | );
47 |
48 | COMMENT ON TABLE "site_admin" IS 'Someone administrative capabilities on the site';
49 |
50 | COMMENT ON COLUMN "site_admin"."password" IS 'A bcrypt-hashed version of the admin password, compared against securely in the JWT Auth API handler for sign-in';
51 |
52 |
53 | CREATE TABLE "order" (
54 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
55 | created_at timestamptz DEFAULT now() NOT NULL,
56 | updated_at timestamptz DEFAULT now() NOT NULL,
57 | user_id int NOT NULL,
58 | billing_address_id int NOT NULL,
59 | shipping_address_id int NOT NULL,
60 | is_shipped boolean DEFAULT FALSE NOT NULL,
61 | order_total numeric,
62 | status text DEFAULT 'CREATED' NOT NULL
63 | );
64 |
65 | COMMENT ON TABLE "order" IS 'An order from a customer, containing one or more products and quantities';
66 |
67 |
68 | CREATE TABLE "order_product" (
69 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
70 | created_at timestamptz DEFAULT now() NOT NULL,
71 | updated_at timestamptz DEFAULT now() NOT NULL,
72 | order_id int NOT NULL,
73 | product_id int NOT NULL,
74 | quantity int NOT NULL
75 | );
76 |
77 | COMMENT ON TABLE "order_product" IS 'A product belonging to a customer order, along with a quantity';
78 |
79 |
80 | CREATE TABLE order_status (
81 | status text PRIMARY KEY
82 | );
83 |
84 | INSERT INTO order_status (
85 | status)
86 | VALUES (
87 | 'CREATED'),
88 | (
89 | 'PAID'),
90 | (
91 | 'SHIPPED'),
92 | (
93 | 'DELIVERED'),
94 | (
95 | 'CANCELLED'),
96 | (
97 | 'REFUNDED');
98 | CREATE TABLE product (
99 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
100 | created_at timestamptz DEFAULT now() NOT NULL,
101 | updated_at timestamptz DEFAULT now() NOT NULL,
102 | name text NOT NULL,
103 | -- Do we want description to be mandatory?
104 | description text,
105 | category_display_name text NOT NULL,
106 | brand text,
107 | price numeric NOT NULL,
108 | -- TODO: Make "product_image_url" table and one-to-many relationship from product -> product_image_url
109 | -- The Kaggle data just came with it in this format so we can hackily (ab)use JSONB for shitty relationships
110 | image_urls jsonb
111 | );
112 |
113 |
114 | -- Hasura Enum table containing product categories
115 | -- Arguably, making categories an Enum might not be a best practice
116 | --
117 | -- For a few reasons:
118 | --
119 | -- Primarily that, because each change to the Enum table (insert/update/delete) modifies the GQL schema types
120 | -- any sort of Client SDK containing the schema types has to be regenerated to stay up-to-date after any changes
121 | --
122 | -- And also because the metadata needs to be reloaded after modifications to the enum, since this can't be detected and reflected automatically yet
123 | --
124 | -- But, this is a great way to demo this feature and a semi-realistic usecase
125 | CREATE TABLE product_category_enum (
126 | name text PRIMARY KEY,
127 | display_name text NOT NULL UNIQUE
128 | );
129 |
130 | INSERT INTO product_category_enum (
131 | display_name,
132 | name)
133 | VALUES (
134 | 'Home Furnishing',
135 | 'home_furnishing'),
136 | (
137 | 'Computers',
138 | 'computers'),
139 | (
140 | 'Baby Care',
141 | 'baby_care'),
142 | (
143 | 'Wearable Smart Devices',
144 | 'wearable_smart_devices'),
145 | (
146 | 'Furniture',
147 | 'furniture'),
148 | (
149 | 'Home Entertainment',
150 | 'home_entertainment'),
151 | (
152 | 'Home & Kitchen',
153 | 'home_kitchen'),
154 | (
155 | 'Clothing',
156 | 'clothing'),
157 | (
158 | 'Beauty and Personal Care',
159 | 'beauty_and_personal_care'),
160 | (
161 | 'Sunglasses',
162 | 'sunglasses'),
163 | (
164 | 'Tools & Hardware',
165 | 'tools_hardware'),
166 | (
167 | 'Household Supplies',
168 | 'household_supplies'),
169 | (
170 | 'Home Improvement',
171 | 'home_improvement'),
172 | (
173 | 'Footwear',
174 | 'footwear'),
175 | (
176 | 'Gaming',
177 | 'gaming'),
178 | (
179 | 'Mobiles & Accessories',
180 | 'mobiles_accessories'),
181 | (
182 | 'Sports & Fitness',
183 | 'sports_fitness'),
184 | (
185 | 'Health & Personal Care Appliances',
186 | 'health_personal_care_appliances'),
187 | (
188 | 'Home Decor & Festive Needs',
189 | 'home_decor_festive_needs'),
190 | (
191 | 'Pens & Stationery',
192 | 'pens_stationery'),
193 | (
194 | 'Watches',
195 | 'watches'),
196 | (
197 | 'Food & Nutrition',
198 | 'food_nutrition'),
199 | (
200 | 'Kitchen & Dining',
201 | 'kitchen_dining'),
202 | (
203 | 'Pet Supplies',
204 | 'pet_supplies'),
205 | (
206 | 'Jewellery',
207 | 'jewellery'),
208 | (
209 | 'Cameras & Accessories',
210 | 'cameras_accessories'),
211 | (
212 | 'Automotive',
213 | 'automotive'),
214 | (
215 | 'eBooks',
216 | 'e_books'),
217 | (
218 | 'Toys & School Supplies',
219 | 'toys_school_supplies'),
220 | (
221 | 'Eyewear',
222 | 'eyewear'),
223 | (
224 | 'Automation & Robotics',
225 | 'automation_robotics'),
226 | (
227 | 'Bags, Wallets & Belts',
228 | 'bags_wallets_belts');
229 |
230 |
231 | CREATE TABLE "product_review" (
232 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
233 | created_at timestamptz DEFAULT now() NOT NULL,
234 | updated_at timestamptz DEFAULT now() NOT NULL,
235 | user_id int NOT NULL,
236 | product_id int NOT NULL,
237 | rating int NOT NULL CHECK (rating >= 1 AND rating <= 5),
238 | -- 5,000 characters = 800 words or 1.5 pages single-spaced (based on 6-char word avg)
239 | comment text NOT NULL CHECK (char_length(comment) <= 5000),
240 | -- Only allow each person to leave one review per product they've purchased
241 | CONSTRAINT one_review_per_person_and_product UNIQUE (user_id, product_id)
242 | );
243 |
244 | COMMENT ON TABLE "product_review" IS 'A review for a product which a customer has purchased before';
245 |
246 |
247 | CREATE TABLE "user" (
248 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
249 | created_at timestamptz DEFAULT now() NOT NULL,
250 | updated_at timestamptz DEFAULT now() NOT NULL,
251 | name text NOT NULL,
252 | email text NOT NULL UNIQUE,
253 | password text NOT NULL,
254 | -- refresh_token really should be NOT NULL but the seed data doesn't have them
255 | refresh_token text UNIQUE
256 | );
257 |
258 | COMMENT ON TABLE "user" IS 'Someone with an account on the site, who uses it to make purchases';
259 |
260 | COMMENT ON COLUMN "user"."password" IS 'A bcrypt-hashed version of the user password, compared against securely in the JWT Auth API handler for sign-in';
261 |
262 |
263 | ALTER TABLE ONLY public.address
264 | ADD CONSTRAINT address_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.user (id);
265 |
266 |
267 | ALTER TABLE ONLY public.order
268 | ADD CONSTRAINT order_billing_address_id_fkey FOREIGN KEY
269 | (billing_address_id) REFERENCES public.address (id);
270 |
271 | ALTER TABLE ONLY public.order
272 | ADD CONSTRAINT order_shipping_address_id_fkey FOREIGN KEY
273 | (shipping_address_id) REFERENCES public.address (id);
274 |
275 | ALTER TABLE ONLY public.order
276 | ADD CONSTRAINT order_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.user (id);
277 |
278 | ALTER TABLE ONLY public.order
279 | ADD CONSTRAINT order_status_fkey FOREIGN KEY (status) REFERENCES
280 | public.order_status (status);
281 |
282 |
283 | /*Functions*/
284 | CREATE OR REPLACE FUNCTION public.gen_order_total ()
285 | RETURNS TRIGGER
286 | LANGUAGE plpgsql
287 | STABLE
288 | AS $function$
289 | DECLARE
290 | sumtotal numeric;
291 | BEGIN
292 | SELECT
293 | TRUNC(SUM(p.price), 2) INTO STRICT sumtotal
294 | FROM
295 | public.order o
296 | INNER JOIN public.order_product op ON (o.id = op.order_id)
297 | INNER JOIN public.product p ON (op.product_id = p.id)
298 | WHERE
299 | o.id = OLD.id
300 | GROUP BY
301 | o.id;
302 | NEW.order_total := sumtotal;
303 | RETURN NEW;
304 | EXCEPTION
305 | WHEN no_data_found THEN
306 | RAISE NOTICE 'No products found for %', OLD.id;
307 | RETURN NEW;
308 | END;
309 |
310 | $function$;
311 |
312 |
313 | ALTER TABLE ONLY public.order_product
314 | ADD CONSTRAINT order_id_fkey FOREIGN KEY (order_id) REFERENCES public.order (id);
315 |
316 | ALTER TABLE ONLY public.order_product
317 | ADD CONSTRAINT product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product (id);
318 |
319 |
320 | ALTER TABLE ONLY public.product
321 | ADD CONSTRAINT category_display_name_fk FOREIGN KEY (category_display_name) REFERENCES public.product_category_enum (display_name);
322 |
323 |
324 | ALTER TABLE ONLY public.product_review
325 | ADD CONSTRAINT user_id_fkey FOREIGN KEY (user_id) REFERENCES public.user (id);
326 |
327 | ALTER TABLE ONLY public.product_review
328 | ADD CONSTRAINT product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product (id);
329 |
330 |
331 | CREATE TRIGGER set_address_updated_at
332 | BEFORE UPDATE ON public.address
333 | FOR EACH ROW
334 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
335 | CREATE TRIGGER set_site_admin_updated_at
336 | BEFORE UPDATE ON public.site_admin
337 | FOR EACH ROW
338 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
339 | CREATE TRIGGER set_order_updated_at
340 | BEFORE UPDATE ON public.order
341 | FOR EACH ROW
342 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
343 |
344 | CREATE TRIGGER sum_order
345 | BEFORE INSERT OR UPDATE ON public.order
346 | FOR EACH ROW
347 | EXECUTE PROCEDURE public.gen_order_total ();
348 | CREATE TRIGGER set_order_product_updated_at
349 | BEFORE UPDATE ON public.order_product
350 | FOR EACH ROW
351 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
352 | CREATE TRIGGER set_product_updated_at
353 | BEFORE UPDATE ON public.product
354 | FOR EACH ROW
355 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
356 |
357 | CREATE TRIGGER set_product_review_updated_at
358 | BEFORE UPDATE ON public.product_review
359 | FOR EACH ROW
360 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
361 | CREATE TRIGGER set_user_updated_at
362 | BEFORE UPDATE ON public.user
363 | FOR EACH ROW
364 | EXECUTE FUNCTION public.set_current_timestamp_updated_at ();
--------------------------------------------------------------------------------
/example/hasura/seeds/default/01_B_user_address_seeds.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 |
3 | insert into public.address (user_id, city, state, zipcode, address_line_one) values
4 | (4, 'Springfield', 'Massachusetts', '01129', '119 Victoria Trail'),
5 | (5, 'Indianapolis', 'Indiana', '46216', '06 Holy Cross Lane'),
6 | (6, 'Athens', 'Georgia', '30610', '1287 Clyde Gallagher Terrace'),
7 | (7, 'Orlando', 'Florida', '32813', '02572 Forest Way'),
8 | (8, 'Reading', 'Pennsylvania', '19605', '83625 Dawn Park'),
9 | (9, 'Daytona Beach', 'Florida', '32128', '9202 Carpenter Park'),
10 | (10, 'El Paso', 'Texas', '88579', '509 Grayhawk Pass'),
11 | (11, 'Tacoma', 'Washington', '98411', '5 Troy Hill'),
12 | (12, 'New Castle', 'Pennsylvania', '16107', '7 Bay Center'),
13 | (13, 'El Paso', 'Texas', '79955', '5763 Mendota Point'),
14 | (14, 'Anaheim', 'California', '92825', '7 Mayer Road'),
15 | (15, 'Dallas', 'Texas', '75372', '31319 Maple Wood Terrace'),
16 | (16, 'Anchorage', 'Alaska', '99599', '111 Bashford Pass'),
17 | (17, 'Tulsa', 'Oklahoma', '74184', '77416 Birchwood Junction'),
18 | (18, 'Oklahoma City', 'Oklahoma', '73109', '74037 Comanche Park'),
19 | (19, 'Fullerton', 'California', '92640', '36599 Pennsylvania Circle'),
20 | (20, 'San Jose', 'California', '95113', '1 Hoffman Center'),
21 | (21, 'Albuquerque', 'New Mexico', '87110', '950 Thompson Crossing'),
22 | (22, 'Oakland', 'California', '94611', '2933 Bellgrove Parkway'),
23 | (23, 'San Diego', 'California', '92153', '8962 Katie Lane'),
24 | (24, 'Amarillo', 'Texas', '79105', '2 Gina Circle'),
25 | (25, 'Lansing', 'Michigan', '48912', '60 Jenna Junction'),
26 | (26, 'Sacramento', 'California', '95823', '2196 Corscot Court'),
27 | (27, 'Elizabeth', 'New Jersey', '07208', '1 Portage Parkway'),
28 | (28, 'Norfolk', 'Virginia', '23514', '9 Knutson Hill'),
29 | (29, 'Indianapolis', 'Indiana', '46266', '2394 Talisman Lane'),
30 | (30, 'Tucson', 'Arizona', '85732', '139 Sherman Point'),
31 | (31, 'El Paso', 'Texas', '79989', '7 Grover Avenue'),
32 | (32, 'Los Angeles', 'California', '90005', '1620 Ilene Place'),
33 | (33, 'Huntington', 'West Virginia', '25770', '93 Macpherson Avenue'),
34 | (34, 'Kansas City', 'Kansas', '66160', '02 Green Ridge Road'),
35 | (35, 'Cincinnati', 'Ohio', '45243', '6 Cascade Road'),
36 | (36, 'Fresno', 'California', '93721', '55 New Castle Trail'),
37 | (37, 'Tulsa', 'Oklahoma', '74156', '2483 Upham Pass'),
38 | (38, 'Miami', 'Florida', '33261', '2 Jackson Drive'),
39 | (39, 'Houston', 'Texas', '77075', '89 Service Parkway'),
40 | (40, 'Lexington', 'Kentucky', '40586', '61159 Onsgard Crossing'),
41 | (41, 'Saint Augustine', 'Florida', '32092', '69 Magdeline Parkway'),
42 | (42, 'San Angelo', 'Texas', '76905', '1043 Novick Court'),
43 | (43, 'Norfolk', 'Virginia', '23520', '62190 Jana Trail'),
44 | (44, 'Canton', 'Ohio', '44710', '0 Eggendart Circle'),
45 | (45, 'Helena', 'Montana', '59623', '2 Thackeray Junction'),
46 | (46, 'Montgomery', 'Alabama', '36177', '012 Dixon Lane'),
47 | (47, 'Troy', 'Michigan', '48098', '4 Bashford Trail'),
48 | (48, 'Amarillo', 'Texas', '79105', '4176 Clyde Gallagher Drive'),
49 | (49, 'Stamford', 'Connecticut', '06922', '5975 Armistice Place'),
50 | (50, 'Rochester', 'New York', '14652', '811 Tony Lane'),
51 | (51, 'Houston', 'Texas', '77095', '737 Steensland Avenue'),
52 | (52, 'Alexandria', 'Virginia', '22333', '4777 Lerdahl Plaza'),
53 | (53, 'Knoxville', 'Tennessee', '37914', '01294 Warbler Crossing'),
54 | (54, 'Valdosta', 'Georgia', '31605', '1 Ramsey Parkway'),
55 | (55, 'Wilmington', 'North Carolina', '28410', '48 Main Alley'),
56 | (56, 'Nashville', 'Tennessee', '37228', '076 Buhler Point'),
57 | (57, 'Pittsburgh', 'Pennsylvania', '15279', '5291 Haas Junction'),
58 | (58, 'Birmingham', 'Alabama', '35205', '94012 Bellgrove Crossing'),
59 | (59, 'San Jose', 'California', '95160', '86685 Rieder Circle'),
60 | (60, 'Dayton', 'Ohio', '45440', '527 Hintze Point'),
61 | (61, 'Saint Louis', 'Missouri', '63196', '1 Kensington Parkway'),
62 | (62, 'Boston', 'Massachusetts', '02208', '45321 Thackeray Way'),
63 | (63, 'Denver', 'Colorado', '80279', '54 Sloan Way'),
64 | (64, 'Albuquerque', 'New Mexico', '87190', '1 Thompson Crossing'),
65 | (65, 'Clearwater', 'Florida', '34629', '00356 Sugar Center'),
66 | (66, 'Knoxville', 'Tennessee', '37939', '69 Ryan Point'),
67 | (67, 'Shreveport', 'Louisiana', '71166', '10473 Brown Circle'),
68 | (68, 'Houston', 'Texas', '77260', '44 Warrior Way'),
69 | (69, 'Albuquerque', 'New Mexico', '87195', '55415 Waywood Crossing'),
70 | (70, 'Lansing', 'Michigan', '48930', '9830 Sycamore Parkway'),
71 | (71, 'Waltham', 'Massachusetts', '02453', '24648 Lakewood Crossing'),
72 | (72, 'Lexington', 'Kentucky', '40505', '30550 Macpherson Parkway'),
73 | (73, 'Phoenix', 'Arizona', '85053', '36184 Eggendart Court'),
74 | (74, 'Gastonia', 'North Carolina', '28055', '5839 Dapin Pass'),
75 | (75, 'Baton Rouge', 'Louisiana', '70883', '1646 Merry Way'),
76 | (76, 'Newark', 'Delaware', '19725', '26795 Swallow Lane'),
77 | (77, 'Ogden', 'Utah', '84409', '3329 Jana Crossing'),
78 | (78, 'Raleigh', 'North Carolina', '27610', '01950 Graedel Park'),
79 | (79, 'San Antonio', 'Texas', '78260', '08 Elmside Alley'),
80 | (80, 'New York City', 'New York', '10170', '0 Swallow Court'),
81 | (81, 'San Antonio', 'Texas', '78240', '4991 Menomonie Junction'),
82 | (82, 'Austin', 'Texas', '78721', '854 Waywood Point'),
83 | (83, 'San Angelo', 'Texas', '76905', '7 Lindbergh Way'),
84 | (84, 'Los Angeles', 'California', '90071', '99 Welch Pass'),
85 | (85, 'Richmond', 'Virginia', '23228', '7 Commercial Road'),
86 | (86, 'Oklahoma City', 'Oklahoma', '73157', '1090 Hoepker Court'),
87 | (87, 'Columbia', 'South Carolina', '29208', '894 Hoard Way'),
88 | (88, 'Roanoke', 'Virginia', '24048', '1584 Rowland Plaza'),
89 | (89, 'Lexington', 'Kentucky', '40510', '0983 Elmside Way'),
90 | (90, 'Portland', 'Oregon', '97232', '264 Eggendart Hill'),
91 | (91, 'Houston', 'Texas', '77070', '54 Debs Terrace'),
92 | (92, 'Corpus Christi', 'Texas', '78410', '65 5th Road'),
93 | (93, 'Arlington', 'Virginia', '22244', '7 Arrowood Trail'),
94 | (94, 'Anchorage', 'Alaska', '99599', '2 Pearson Place'),
95 | (95, 'Canton', 'Ohio', '44760', '5 Lawn Court'),
96 | (96, 'Arlington', 'Virginia', '22234', '0607 Rockefeller Point'),
97 | (97, 'Saginaw', 'Michigan', '48604', '68566 Mallard Crossing'),
98 | (98, 'Detroit', 'Michigan', '48242', '7513 Prairieview Trail'),
99 | (99, 'Tampa', 'Florida', '33686', '7928 Golf Lane'),
100 | (100, 'San Francisco', 'California', '94154', '69 Burrows Way'),
101 | (101, 'Saginaw', 'Michigan', '48604', '8 Mesta Parkway'),
102 | (102, 'Akron', 'Ohio', '44305', '0316 Nova Street'),
103 | (103, 'Fresno', 'California', '93715', '8 Onsgard Drive'),
104 | (104, 'Boston', 'Massachusetts', '02298', '7134 Lawn Alley'),
105 | (105, 'Frederick', 'Maryland', '21705', '6471 Muir Lane'),
106 | (106, 'Salt Lake City', 'Utah', '84115', '3384 Carberry Alley'),
107 | (107, 'Washington', 'District of Columbia', '20057', '013 Thompson Place'),
108 | (108, 'Cleveland', 'Ohio', '44191', '17 Bowman Parkway'),
109 | (109, 'Saint Cloud', 'Minnesota', '56372', '1 Del Sol Way'),
110 | (110, 'El Paso', 'Texas', '79994', '1919 Ludington Pass'),
111 | (111, 'Allentown', 'Pennsylvania', '18105', '17647 Shoshone Road'),
112 | (112, 'Bronx', 'New York', '10459', '0 Namekagon Lane'),
113 | (113, 'Corona', 'California', '92878', '11 Monterey Pass'),
114 | (114, 'Pittsburgh', 'Pennsylvania', '15286', '206 Novick Parkway'),
115 | (115, 'Washington', 'District of Columbia', '20099', '73 Anzinger Trail'),
116 | (116, 'Jamaica', 'New York', '11436', '66 Meadow Vale Point'),
117 | (117, 'Washington', 'District of Columbia', '20456', '4675 Menomonie Hill'),
118 | (118, 'Cincinnati', 'Ohio', '45203', '9 La Follette Crossing'),
119 | (119, 'Jacksonville', 'Florida', '32215', '996 South Parkway'),
120 | (120, 'Greenville', 'South Carolina', '29605', '0106 Myrtle Hill'),
121 | (121, 'Shawnee Mission', 'Kansas', '66225', '0083 Gerald Park'),
122 | (122, 'Washington', 'District of Columbia', '20041', '72954 Loftsgordon Trail'),
123 | (123, 'San Antonio', 'Texas', '78260', '45 Elmside Terrace'),
124 | (124, 'Bronx', 'New York', '10454', '8867 Namekagon Hill'),
125 | (125, 'Corona', 'California', '92878', '17 Vidon Avenue'),
126 | (126, 'Moreno Valley', 'California', '92555', '933 Clyde Gallagher Parkway'),
127 | (127, 'Memphis', 'Tennessee', '38114', '3660 Colorado Lane'),
128 | (128, 'Bakersfield', 'California', '93381', '1557 Granby Alley'),
129 | (129, 'Gulfport', 'Mississippi', '39505', '357 Spaight Way'),
130 | (130, 'Evansville', 'Indiana', '47737', '366 Manley Court'),
131 | (131, 'Amarillo', 'Texas', '79159', '565 Michigan Road'),
132 | (132, 'Richmond', 'Virginia', '23220', '5 Hovde Road'),
133 | (133, 'Las Cruces', 'New Mexico', '88006', '680 Graceland Parkway'),
134 | (134, 'Albuquerque', 'New Mexico', '87110', '26 Shelley Avenue'),
135 | (135, 'Henderson', 'Nevada', '89012', '4 Sunnyside Place'),
136 | (136, 'Saint Paul', 'Minnesota', '55123', '01 3rd Way'),
137 | (137, 'Huntsville', 'Texas', '77343', '04 Trailsway Crossing'),
138 | (138, 'Saint Paul', 'Minnesota', '55172', '454 Bayside Crossing'),
139 | (139, 'El Paso', 'Texas', '79940', '91257 Comanche Hill'),
140 | (140, 'Corpus Christi', 'Texas', '78465', '8884 Lukken Crossing'),
141 | (141, 'Kalamazoo', 'Michigan', '49006', '05191 Orin Pass'),
142 | (142, 'Kansas City', 'Missouri', '64125', '68977 Porter Crossing'),
143 | (143, 'Kansas City', 'Missouri', '64114', '5367 Fieldstone Crossing'),
144 | (144, 'Chicago', 'Illinois', '60663', '6997 Loomis Trail'),
145 | (145, 'Tacoma', 'Washington', '98481', '8688 Dryden Crossing'),
146 | (146, 'Fort Lauderdale', 'Florida', '33320', '7 Warrior Place'),
147 | (147, 'San Diego', 'California', '92115', '53367 Darwin Plaza'),
148 | (148, 'Washington', 'District of Columbia', '20414', '1 Fairfield Plaza'),
149 | (149, 'Metairie', 'Louisiana', '70005', '1 Maryland Parkway'),
150 | (150, 'New York City', 'New York', '10155', '53 Johnson Place'),
151 | (151, 'Midland', 'Michigan', '48670', '945 Gateway Point'),
152 | (152, 'Lakeland', 'Florida', '33811', '3554 Pennsylvania Alley'),
153 | (153, 'Fort Lauderdale', 'Florida', '33345', '2458 Forest Place'),
154 | (154, 'Raleigh', 'North Carolina', '27635', '26 Huxley Way'),
155 | (155, 'Washington', 'District of Columbia', '20310', '03 Sommers Lane'),
156 | (156, 'Richmond', 'Virginia', '23277', '24775 Mallard Place'),
157 | (157, 'Littleton', 'Colorado', '80126', '09132 Myrtle Trail'),
158 | (158, 'Los Angeles', 'California', '90071', '625 Warbler Street'),
159 | (159, 'Washington', 'District of Columbia', '20503', '8 Green Plaza'),
160 | (160, 'Palatine', 'Illinois', '60078', '349 5th Street'),
161 | (161, 'Philadelphia', 'Pennsylvania', '19184', '01905 Amoth Circle'),
162 | (162, 'Bradenton', 'Florida', '34205', '5200 John Wall Crossing'),
163 | (163, 'Carson City', 'Nevada', '89714', '2323 Florence Court'),
164 | (164, 'Irving', 'Texas', '75037', '466 Charing Cross Avenue'),
165 | (165, 'Waterbury', 'Connecticut', '06726', '993 Anniversary Junction'),
166 | (166, 'Richmond', 'Virginia', '23272', '90896 Stone Corner Lane'),
167 | (167, 'Atlanta', 'Georgia', '30375', '2 Armistice Lane'),
168 | (168, 'Mesquite', 'Texas', '75185', '0 Weeping Birch Court'),
169 | (169, 'Los Angeles', 'California', '90060', '916 Butterfield Terrace'),
170 | (170, 'Seattle', 'Washington', '98104', '82201 Clarendon Avenue'),
171 | (171, 'Detroit', 'Michigan', '48275', '62567 Walton Circle'),
172 | (172, 'Arlington', 'Texas', '76004', '897 Anniversary Place'),
173 | (173, 'Austin', 'Texas', '78737', '309 Fisk Road'),
174 | (174, 'Austin', 'Texas', '78744', '1 Burning Wood Circle'),
175 | (175, 'Miami', 'Florida', '33233', '30120 Truax Point'),
176 | (176, 'Sacramento', 'California', '94263', '47485 Sauthoff Junction'),
177 | (177, 'Denver', 'Colorado', '80209', '704 Morrow Park'),
178 | (178, 'Cumming', 'Georgia', '30130', '61 Homewood Plaza'),
179 | (179, 'Watertown', 'Massachusetts', '02472', '443 Bonner Park'),
180 | (180, 'Houston', 'Texas', '77035', '454 Sycamore Drive'),
181 | (181, 'Monticello', 'Minnesota', '55590', '3 Carpenter Crossing'),
182 | (182, 'Colorado Springs', 'Colorado', '80945', '8 Red Cloud Lane'),
183 | (183, 'Los Angeles', 'California', '90101', '750 Loftsgordon Crossing'),
184 | (184, 'Escondido', 'California', '92030', '93634 Farwell Center'),
185 | (185, 'Racine', 'Wisconsin', '53405', '45 Golf View Point'),
186 | (186, 'Oakland', 'California', '94616', '29 Northview Avenue'),
187 | (187, 'Washington', 'District of Columbia', '20205', '2 Sommers Place'),
188 | (188, 'Las Vegas', 'Nevada', '89120', '31088 Warbler Terrace'),
189 | (189, 'Kansas City', 'Missouri', '64149', '413 Nevada Street'),
190 | (190, 'Fort Worth', 'Texas', '76115', '20 Colorado Place'),
191 | (191, 'Fort Worth', 'Texas', '76178', '35 Duke Crossing'),
192 | (192, 'Southfield', 'Michigan', '48076', '694 Monica Alley'),
193 | (193, 'Tampa', 'Florida', '33686', '5 Katie Park'),
194 | (194, 'Santa Fe', 'New Mexico', '87505', '3 Porter Lane'),
195 | (195, 'Saint Petersburg', 'Florida', '33705', '4 Autumn Leaf Court'),
196 | (196, 'Fort Smith', 'Arkansas', '72916', '6489 Loomis Center'),
197 | (197, 'Galveston', 'Texas', '77554', '80 Hanson Alley'),
198 | (198, 'Oklahoma City', 'Oklahoma', '73135', '9 Mayer Junction'),
199 | (199, 'San Antonio', 'Texas', '78220', '7367 Merry Junction'),
200 | (200, 'Elmira', 'New York', '14905', '066 Anzinger Point'),
201 | (201, 'West Hartford', 'Connecticut', '06127', '8386 Dawn Parkway'),
202 | (202, 'Davenport', 'Iowa', '52804', '8 Melrose Parkway'),
203 | (203, 'Washington', 'District of Columbia', '20530', '336 Rockefeller Park'),
204 | (204, 'Garland', 'Texas', '75044', '52 Talmadge Center'),
205 | (205, 'Springfield', 'Illinois', '62756', '9785 Birchwood Crossing'),
206 | (206, 'Tucson', 'Arizona', '85754', '61256 Meadow Valley Junction'),
207 | (207, 'San Jose', 'California', '95113', '1 Magdeline Park'),
208 | (208, 'El Paso', 'Texas', '79950', '8 Gateway Court'),
209 | (209, 'Dallas', 'Texas', '75387', '125 Muir Parkway'),
210 | (210, 'Clearwater', 'Florida', '34620', '1 Thompson Junction'),
211 | (211, 'Staten Island', 'New York', '10310', '44953 Clemons Way'),
212 | (212, 'Simi Valley', 'California', '93094', '941 Sage Trail'),
213 | (213, 'Atlanta', 'Georgia', '30358', '831 Ohio Way'),
214 | (214, 'Washington', 'District of Columbia', '20310', '49614 Sherman Court'),
215 | (215, 'Jackson', 'Mississippi', '39204', '22747 Fremont Street'),
216 | (216, 'Birmingham', 'Alabama', '35210', '871 Russell Point'),
217 | (217, 'Irving', 'Texas', '75062', '6 Melvin Way'),
218 | (218, 'Charleston', 'West Virginia', '25313', '6836 Clyde Gallagher Plaza'),
219 | (219, 'Metairie', 'Louisiana', '70033', '6 Eagle Crest Center'),
220 | (220, 'Fort Lauderdale', 'Florida', '33345', '07240 Commercial Center'),
221 | (221, 'Lexington', 'Kentucky', '40586', '4 Mcguire Center'),
222 | (222, 'Portland', 'Oregon', '97216', '8 Amoth Place'),
223 | (223, 'Bridgeport', 'Connecticut', '06606', '44 Kedzie Terrace'),
224 | (224, 'New York City', 'New York', '10079', '16 Novick Alley'),
225 | (225, 'Orlando', 'Florida', '32819', '920 Alpine Point'),
226 | (226, 'Bozeman', 'Montana', '59771', '1470 Vermont Court'),
227 | (227, 'New Orleans', 'Louisiana', '70124', '97640 Ruskin Crossing'),
228 | (228, 'Boston', 'Massachusetts', '02283', '5092 Union Point'),
229 | (229, 'Colorado Springs', 'Colorado', '80940', '563 Dunning Trail'),
230 | (230, 'Fort Wayne', 'Indiana', '46852', '82 Elka Plaza'),
231 | (231, 'Baltimore', 'Maryland', '21290', '8018 Ludington Way'),
232 | (232, 'Birmingham', 'Alabama', '35236', '8 Grim Junction'),
233 | (233, 'New York City', 'New York', '10105', '1856 Anderson Way'),
234 | (234, 'Seattle', 'Washington', '98158', '89 Merry Circle'),
235 | (235, 'Chicago', 'Illinois', '60636', '5539 Starling Junction'),
236 | (236, 'Peoria', 'Illinois', '61656', '4373 Arapahoe Way'),
237 | (237, 'Visalia', 'California', '93291', '295 Stephen Trail'),
238 | (238, 'Pensacola', 'Florida', '32575', '15 Quincy Trail'),
239 | (239, 'El Paso', 'Texas', '79989', '83 Southridge Court'),
240 | (240, 'Detroit', 'Michigan', '48211', '729 Lakewood Gardens Road'),
241 | (241, 'El Paso', 'Texas', '79968', '97598 Continental Parkway'),
242 | (242, 'Silver Spring', 'Maryland', '20910', '66055 4th Point'),
243 | (243, 'Boston', 'Massachusetts', '02109', '7 Lunder Circle'),
244 | (244, 'Fort Worth', 'Texas', '76162', '14 Bunker Hill Way'),
245 | (245, 'Atlanta', 'Georgia', '30323', '06215 Hoepker Alley'),
246 | (246, 'Shreveport', 'Louisiana', '71166', '660 Logan Crossing'),
247 | (247, 'Birmingham', 'Alabama', '35220', '66 Browning Road'),
248 | (248, 'Springfield', 'Missouri', '65898', '2 Marquette Circle'),
249 | (249, 'El Paso', 'Texas', '88519', '2 Jay Circle'),
250 | (250, 'Wilmington', 'North Carolina', '28410', '5263 Hoepker Lane'),
251 | (251, 'Pensacola', 'Florida', '32505', '44544 Browning Drive'),
252 | (252, 'Escondido', 'California', '92030', '86933 Loomis Junction'),
253 | (253, 'Nashville', 'Tennessee', '37235', '8539 Caliangt Crossing'),
254 | (254, 'El Paso', 'Texas', '79955', '8 Luster Trail'),
255 | (255, 'Jamaica', 'New York', '11431', '627 Stephen Hill'),
256 | (256, 'New Orleans', 'Louisiana', '70142', '7 Hauk Lane'),
257 | (257, 'Bronx', 'New York', '10454', '4 Ramsey Place'),
258 | (258, 'Lincoln', 'Nebraska', '68505', '6 Morrow Hill'),
259 | (259, 'Tucson', 'Arizona', '85710', '6790 Shopko Pass'),
260 | (260, 'Washington', 'District of Columbia', '20067', '5879 Bartillon Park'),
261 | (261, 'Charleston', 'West Virginia', '25331', '8897 Blaine Crossing'),
262 | (262, 'Fresno', 'California', '93715', '04 6th Trail'),
263 | (263, 'Elmira', 'New York', '14905', '383 Roth Crossing'),
264 | (264, 'Saint Louis', 'Missouri', '63167', '12642 David Parkway'),
265 | (265, 'Las Vegas', 'Nevada', '89110', '9371 Reinke Center'),
266 | (266, 'Madison', 'Wisconsin', '53710', '801 Northview Circle'),
267 | (267, 'Los Angeles', 'California', '90025', '1 Macpherson Hill'),
268 | (268, 'Houston', 'Texas', '77281', '48 Prentice Road'),
269 | (269, 'Columbia', 'South Carolina', '29208', '789 Goodland Pass'),
270 | (270, 'Santa Barbara', 'California', '93111', '062 Lerdahl Way'),
271 | (271, 'Greensboro', 'North Carolina', '27499', '61 Beilfuss Terrace'),
272 | (272, 'Fullerton', 'California', '92835', '3 Forest Run Lane'),
273 | (273, 'Albany', 'New York', '12262', '317 Forest Run Crossing'),
274 | (274, 'Anchorage', 'Alaska', '99522', '5 Esch Street'),
275 | (275, 'Salt Lake City', 'Utah', '84199', '59 Glacier Hill Circle'),
276 | (276, 'Jackson', 'Mississippi', '39282', '2 Golf View Avenue'),
277 | (277, 'Austin', 'Texas', '78710', '16780 Rockefeller Point'),
278 | (278, 'Gainesville', 'Florida', '32605', '16 Homewood Pass'),
279 | (279, 'Washington', 'District of Columbia', '20425', '81271 Karstens Parkway'),
280 | (280, 'Saint Louis', 'Missouri', '63110', '99256 Bobwhite Parkway'),
281 | (281, 'Boston', 'Massachusetts', '02216', '6 Fairfield Place'),
282 | (282, 'Minneapolis', 'Minnesota', '55446', '2156 Truax Court'),
283 | (283, 'Indianapolis', 'Indiana', '46231', '6034 Thompson Avenue'),
284 | (284, 'Tucson', 'Arizona', '85710', '3 Sachs Terrace'),
285 | (285, 'Providence', 'Rhode Island', '02905', '47009 Maryland Court'),
286 | (286, 'Omaha', 'Nebraska', '68134', '1 Nova Alley'),
287 | (287, 'Visalia', 'California', '93291', '706 Moulton Drive'),
288 | (288, 'Danbury', 'Connecticut', '06816', '0806 Park Meadow Place'),
289 | (289, 'Minneapolis', 'Minnesota', '55428', '48 Kennedy Hill'),
290 | (290, 'Lexington', 'Kentucky', '40515', '431 Browning Hill'),
291 | (291, 'Lincoln', 'Nebraska', '68531', '898 Longview Hill'),
292 | (292, 'Mobile', 'Alabama', '36616', '800 Hazelcrest Center'),
293 | (293, 'Chicago', 'Illinois', '60609', '0 Crownhardt Road'),
294 | (294, 'New York City', 'New York', '10105', '0 Rieder Junction'),
295 | (295, 'Orlando', 'Florida', '32835', '1 Mallard Way'),
296 | (296, 'San Francisco', 'California', '94147', '251 Acker Circle'),
297 | (297, 'Erie', 'Pennsylvania', '16565', '985 Dryden Crossing'),
298 | (298, 'Cleveland', 'Ohio', '44125', '74 Fuller Parkway'),
299 | (299, 'Oakland', 'California', '94605', '450 Shelley Place'),
300 | (300, 'Phoenix', 'Arizona', '85015', '89 Barnett Crossing'),
301 | (301, 'Macon', 'Georgia', '31296', '0 Bayside Point'),
302 | (302, 'Pinellas Park', 'Florida', '34665', '53 Duke Avenue'),
303 | (303, 'Jacksonville', 'Florida', '32204', '9666 Riverside Hill'),
304 | (304, 'Fredericksburg', 'Virginia', '22405', '301 Cody Center'),
305 | (305, 'El Paso', 'Texas', '79940', '2218 Washington Parkway'),
306 | (306, 'New York City', 'New York', '10150', '974 Aberg Pass'),
307 | (307, 'San Jose', 'California', '95160', '08109 Mayer Alley'),
308 | (308, 'Arlington', 'Virginia', '22212', '0985 Clarendon Trail'),
309 | (309, 'Baton Rouge', 'Louisiana', '70836', '8 Buell Street'),
310 | (310, 'South Bend', 'Indiana', '46614', '965 Sullivan Avenue'),
311 | (311, 'Austin', 'Texas', '78769', '48226 Straubel Junction'),
312 | (312, 'Kansas City', 'Missouri', '64190', '71300 Oak Valley Point'),
313 | (313, 'Greensboro', 'North Carolina', '27404', '19687 Maywood Drive'),
314 | (314, 'Jacksonville', 'Florida', '32204', '9 Rusk Parkway'),
315 | (315, 'Evansville', 'Indiana', '47719', '29 Donald Street'),
316 | (316, 'Lincoln', 'Nebraska', '68510', '88 Talisman Crossing'),
317 | (317, 'Los Angeles', 'California', '90035', '1036 Chive Street'),
318 | (318, 'Temple', 'Texas', '76505', '8730 Aberg Terrace'),
319 | (319, 'Waterbury', 'Connecticut', '06726', '3 2nd Trail'),
320 | (320, 'Atlanta', 'Georgia', '30392', '5 Namekagon Plaza'),
321 | (321, 'Terre Haute', 'Indiana', '47812', '31 Ilene Parkway'),
322 | (322, 'Pensacola', 'Florida', '32505', '74910 Bowman Avenue'),
323 | (323, 'Los Angeles', 'California', '90087', '527 Beilfuss Terrace'),
324 | (324, 'San Francisco', 'California', '94121', '19 Debs Terrace'),
325 | (325, 'Saint Louis', 'Missouri', '63116', '2 Mallard Court'),
326 | (326, 'Aurora', 'Colorado', '80015', '80010 Homewood Street'),
327 | (327, 'Seattle', 'Washington', '98140', '30 Troy Center'),
328 | (328, 'Kansas City', 'Kansas', '66160', '725 Brown Way'),
329 | (329, 'San Diego', 'California', '92153', '32 Green Ridge Hill'),
330 | (330, 'Springfield', 'Illinois', '62711', '53161 Tony Alley'),
331 | (331, 'Colorado Springs', 'Colorado', '80925', '14636 Forster Lane'),
332 | (332, 'Atlanta', 'Georgia', '30386', '90746 Lindbergh Park'),
333 | (333, 'Riverside', 'California', '92519', '58 Prairieview Pass'),
334 | (334, 'Des Moines', 'Iowa', '50981', '24 Sugar Way'),
335 | (335, 'Durham', 'North Carolina', '27717', '670 American Ash Street'),
336 | (336, 'Topeka', 'Kansas', '66617', '4407 Hollow Ridge Court'),
337 | (337, 'Cincinnati', 'Ohio', '45238', '60 Fisk Circle'),
338 | (338, 'Evansville', 'Indiana', '47747', '10893 Cascade Center'),
339 | (339, 'Los Angeles', 'California', '90076', '013 Gerald Crossing'),
340 | (340, 'Houston', 'Texas', '77288', '131 Pennsylvania Plaza'),
341 | (341, 'Jamaica', 'New York', '11407', '084 Kensington Circle'),
342 | (342, 'Seattle', 'Washington', '98121', '6117 Center Drive'),
343 | (343, 'West Hartford', 'Connecticut', '06127', '4 Red Cloud Alley'),
344 | (344, 'Sioux Falls', 'South Dakota', '57105', '5 Mcbride Crossing'),
345 | (345, 'Buffalo', 'New York', '14210', '148 Mayer Crossing'),
346 | (346, 'Springfield', 'Missouri', '65805', '98 Westend Drive'),
347 | (347, 'New York City', 'New York', '10270', '9 Spohn Park'),
348 | (348, 'Arlington', 'Virginia', '22244', '3786 Farragut Circle'),
349 | (349, 'New Orleans', 'Louisiana', '70174', '8674 Kinsman Park'),
350 | (350, 'Buffalo', 'New York', '14210', '951 Washington Way'),
351 | (351, 'Baton Rouge', 'Louisiana', '70820', '07 Manufacturers Court'),
352 | (352, 'Nashville', 'Tennessee', '37210', '518 Butternut Point'),
353 | (353, 'Seattle', 'Washington', '98121', '47 Graedel Street'),
354 | (354, 'Nashville', 'Tennessee', '37235', '6 Dunning Avenue'),
355 | (355, 'Orlando', 'Florida', '32813', '310 Sutherland Drive'),
356 | (356, 'Cleveland', 'Ohio', '44105', '5 Badeau Parkway'),
357 | (357, 'Monroe', 'Louisiana', '71208', '2833 Manley Trail'),
358 | (358, 'Des Moines', 'Iowa', '50315', '132 Dovetail Plaza'),
359 | (359, 'Bethesda', 'Maryland', '20816', '10859 Mccormick Drive'),
360 | (360, 'New Haven', 'Connecticut', '06520', '593 Schurz Place'),
361 | (361, 'Terre Haute', 'Indiana', '47805', '459 Quincy Alley'),
362 | (362, 'Lakeland', 'Florida', '33811', '52 Goodland Way'),
363 | (363, 'Roanoke', 'Virginia', '24014', '518 Armistice Center'),
364 | (364, 'Tampa', 'Florida', '33673', '3722 Larry Plaza'),
365 | (365, 'Fresno', 'California', '93762', '36096 Shopko Road'),
366 | (366, 'Winston Salem', 'North Carolina', '27110', '98 Spenser Pass'),
367 | (367, 'Chicago', 'Illinois', '60657', '29 Lawn Hill'),
368 | (368, 'Kansas City', 'Missouri', '64199', '72339 Boyd Center'),
369 | (369, 'Canton', 'Ohio', '44720', '9 Butternut Drive'),
370 | (370, 'Lawrenceville', 'Georgia', '30245', '350 Commercial Trail'),
371 | (371, 'Humble', 'Texas', '77346', '64 Rutledge Point'),
372 | (372, 'Newton', 'Massachusetts', '02162', '44549 1st Street'),
373 | (373, 'El Paso', 'Texas', '88530', '28836 Northwestern Park'),
374 | (374, 'Inglewood', 'California', '90305', '0 Melby Park'),
375 | (375, 'El Paso', 'Texas', '88574', '161 Schurz Place'),
376 | (376, 'Salem', 'Oregon', '97312', '4370 Dixon Circle'),
377 | (377, 'Philadelphia', 'Pennsylvania', '19104', '5652 Katie Avenue'),
378 | (378, 'Johnson City', 'Tennessee', '37605', '01234 Goodland Trail'),
379 | (379, 'Iowa City', 'Iowa', '52245', '75 Nevada Pass'),
380 | (380, 'San Diego', 'California', '92145', '6662 Daystar Drive'),
381 | (381, 'Saint Paul', 'Minnesota', '55166', '423 Almo Center'),
382 | (382, 'Anchorage', 'Alaska', '99512', '5 Stang Street'),
383 | (383, 'Seattle', 'Washington', '98140', '4200 Shoshone Terrace'),
384 | (384, 'Austin', 'Texas', '78778', '3030 Jackson Park'),
385 | (385, 'Salt Lake City', 'Utah', '84170', '4 Vermont Alley'),
386 | (386, 'Lynchburg', 'Virginia', '24515', '671 Warbler Avenue'),
387 | (387, 'Reading', 'Pennsylvania', '19610', '8 Fairview Place'),
388 | (388, 'Omaha', 'Nebraska', '68117', '5361 Dottie Way'),
389 | (389, 'Washington', 'District of Columbia', '20057', '569 Vera Court'),
390 | (390, 'Houston', 'Texas', '77015', '12745 Veith Lane'),
391 | (391, 'Minneapolis', 'Minnesota', '55487', '08545 Kim Pass'),
392 | (392, 'Philadelphia', 'Pennsylvania', '19120', '579 Carioca Center'),
393 | (393, 'Longview', 'Texas', '75605', '4 Schlimgen Crossing'),
394 | (394, 'Las Vegas', 'Nevada', '89193', '963 Lunder Pass'),
395 | (395, 'Philadelphia', 'Pennsylvania', '19146', '56808 Bellgrove Parkway'),
396 | (396, 'Corpus Christi', 'Texas', '78475', '20111 Green Place'),
397 | (397, 'Worcester', 'Massachusetts', '01654', '61 Ridgeview Crossing'),
398 | (398, 'Tallahassee', 'Florida', '32309', '7 Scoville Terrace'),
399 | (399, 'Bonita Springs', 'Florida', '34135', '8294 Nova Avenue'),
400 | (400, 'Charlotte', 'North Carolina', '28225', '409 Anthes Court'),
401 | (401, 'Colorado Springs', 'Colorado', '80925', '1 Vernon Plaza'),
402 | (402, 'Chicago', 'Illinois', '60669', '38741 Iowa Alley'),
403 | (403, 'Colorado Springs', 'Colorado', '80910', '9 Kensington Place'),
404 | (404, 'York', 'Pennsylvania', '17405', '2 Meadow Vale Junction'),
405 | (405, 'Philadelphia', 'Pennsylvania', '19120', '88364 Transport Center'),
406 | (406, 'Port Washington', 'New York', '11054', '84 Jackson Road'),
407 | (407, 'Houston', 'Texas', '77065', '35541 Chive Court'),
408 | (408, 'Port Charlotte', 'Florida', '33954', '4377 Union Drive'),
409 | (409, 'Terre Haute', 'Indiana', '47812', '281 Redwing Lane'),
410 | (410, 'Cape Coral', 'Florida', '33915', '2439 Doe Crossing Parkway'),
411 | (411, 'Phoenix', 'Arizona', '85083', '5 Oak Valley Avenue'),
412 | (412, 'Harrisburg', 'Pennsylvania', '17105', '566 Eagle Crest Point'),
413 | (413, 'Topeka', 'Kansas', '66642', '002 Harbort Street'),
414 | (414, 'Harrisburg', 'Pennsylvania', '17105', '60129 Bultman Pass'),
415 | (415, 'Norfolk', 'Virginia', '23504', '73880 Kings Parkway'),
416 | (416, 'North Little Rock', 'Arkansas', '72118', '654 Prairieview Lane'),
417 | (417, 'Washington', 'District of Columbia', '20456', '78871 Hallows Court'),
418 | (418, 'San Jose', 'California', '95150', '00 Lillian Trail'),
419 | (419, 'Savannah', 'Georgia', '31410', '69881 Doe Crossing Place'),
420 | (420, 'Dearborn', 'Michigan', '48126', '8162 Sutherland Place'),
421 | (421, 'Akron', 'Ohio', '44315', '1 Summerview Trail'),
422 | (422, 'Sacramento', 'California', '94297', '85 Namekagon Road'),
423 | (423, 'Omaha', 'Nebraska', '68105', '1 Hagan Hill'),
424 | (424, 'Des Moines', 'Iowa', '50315', '41077 Muir Way'),
425 | (425, 'New York City', 'New York', '10275', '374 Warrior Crossing'),
426 | (426, 'Delray Beach', 'Florida', '33448', '5 Lotheville Center'),
427 | (427, 'Columbia', 'South Carolina', '29220', '27 Dottie Drive'),
428 | (428, 'Phoenix', 'Arizona', '85072', '877 Rigney Plaza'),
429 | (429, 'Wilmington', 'Delaware', '19805', '2 Crescent Oaks Avenue'),
430 | (430, 'Garland', 'Texas', '75044', '4434 Vernon Alley'),
431 | (431, 'Longview', 'Texas', '75605', '07509 Lunder Park'),
432 | (432, 'Richmond', 'Virginia', '23260', '35 Pennsylvania Court'),
433 | (433, 'El Paso', 'Texas', '79934', '31 Dawn Pass'),
434 | (434, 'Washington', 'District of Columbia', '20599', '334 Rowland Center'),
435 | (435, 'San Jose', 'California', '95160', '4133 Brown Hill'),
436 | (436, 'Philadelphia', 'Pennsylvania', '19172', '862 Shelley Junction'),
437 | (437, 'Plano', 'Texas', '75074', '858 Marquette Avenue'),
438 | (438, 'Greensboro', 'North Carolina', '27455', '1 Buell Trail'),
439 | (439, 'Peoria', 'Illinois', '61614', '06 Maryland Center'),
440 | (440, 'Richmond', 'Virginia', '23208', '135 Sunnyside Street'),
441 | (441, 'San Francisco', 'California', '94132', '44443 Texas Park'),
442 | (442, 'Houston', 'Texas', '77218', '5547 Jenifer Hill'),
443 | (443, 'Athens', 'Georgia', '30605', '057 Farmco Alley'),
444 | (444, 'Washington', 'District of Columbia', '20580', '39838 Londonderry Trail'),
445 | (445, 'New York City', 'New York', '10060', '767 Westerfield Street'),
446 | (446, 'Santa Fe', 'New Mexico', '87592', '97565 Nelson Court'),
447 | (447, 'Brockton', 'Massachusetts', '02405', '418 Bonner Park'),
448 | (448, 'Newark', 'New Jersey', '07112', '7868 Muir Hill'),
449 | (449, 'Columbia', 'Missouri', '65211', '61 Karstens Place'),
450 | (450, 'Washington', 'District of Columbia', '20299', '39431 Sloan Park'),
451 | (451, 'Orange', 'California', '92668', '17 Laurel Trail'),
452 | (452, 'Denver', 'Colorado', '80243', '15 Columbus Center'),
453 | (453, 'Seattle', 'Washington', '98166', '097 Brentwood Terrace'),
454 | (454, 'Saginaw', 'Michigan', '48604', '48315 Graedel Center'),
455 | (455, 'Houston', 'Texas', '77040', '273 Surrey Road'),
456 | (456, 'Whittier', 'California', '90605', '0698 Jenna Trail'),
457 | (457, 'Fairbanks', 'Alaska', '99790', '91 Anderson Pass'),
458 | (458, 'Springfield', 'Missouri', '65810', '20 Namekagon Road'),
459 | (459, 'Grand Forks', 'North Dakota', '58207', '3563 Karstens Park'),
460 | (460, 'Baltimore', 'Maryland', '21281', '71 Bartelt Way'),
461 | (461, 'Chicago', 'Illinois', '60663', '40 Mesta Plaza'),
462 | (462, 'Lincoln', 'Nebraska', '68517', '9 Hagan Point'),
463 | (463, 'Jacksonville', 'Florida', '32204', '8335 Novick Crossing'),
464 | (464, 'Durham', 'North Carolina', '27710', '09344 Stephen Pass'),
465 | (465, 'Orlando', 'Florida', '32868', '4 Susan Street'),
466 | (466, 'Houston', 'Texas', '77212', '173 Pearson Parkway'),
467 | (467, 'Midland', 'Texas', '79710', '7105 Lakewood Circle'),
468 | (468, 'Cleveland', 'Ohio', '44130', '82 High Crossing Center'),
469 | (469, 'Salt Lake City', 'Utah', '84120', '5973 1st Junction'),
470 | (470, 'Santa Clara', 'California', '95054', '6470 Sundown Crossing'),
471 | (471, 'Roanoke', 'Virginia', '24014', '87 Hoard Center'),
472 | (472, 'Rochester', 'Minnesota', '55905', '02920 Ronald Regan Place'),
473 | (473, 'Erie', 'Pennsylvania', '16565', '93 Cody Point'),
474 | (474, 'Tampa', 'Florida', '33686', '673 Kipling Street'),
475 | (475, 'Lancaster', 'Pennsylvania', '17622', '38290 Forest Run Lane'),
476 | (476, 'Saint Louis', 'Missouri', '63121', '6 Fallview Avenue'),
477 | (477, 'San Francisco', 'California', '94105', '2 Lighthouse Bay Point'),
478 | (478, 'Saint Louis', 'Missouri', '63158', '4 Waywood Court'),
479 | (479, 'San Diego', 'California', '92196', '5 Hooker Terrace'),
480 | (480, 'Jefferson City', 'Missouri', '65105', '52337 Nobel Plaza'),
481 | (481, 'Carson City', 'Nevada', '89706', '08232 Fairfield Circle'),
482 | (482, 'El Paso', 'Texas', '79994', '3602 Arapahoe Street'),
483 | (483, 'Norman', 'Oklahoma', '73071', '7303 Bonner Parkway'),
484 | (484, 'Seattle', 'Washington', '98166', '47914 Schurz Hill'),
485 | (485, 'Miami', 'Florida', '33147', '4864 Waxwing Alley'),
486 | (486, 'Pasadena', 'California', '91186', '7637 Northland Park'),
487 | (487, 'Salem', 'Oregon', '97312', '9637 Brown Circle'),
488 | (488, 'Columbia', 'Missouri', '65211', '3646 Bluejay Lane'),
489 | (489, 'Dallas', 'Texas', '75323', '52 Warrior Park'),
490 | (490, 'Buffalo', 'New York', '14276', '265 John Wall Court'),
491 | (491, 'San Jose', 'California', '95138', '6184 Russell Crossing'),
492 | (492, 'Laredo', 'Texas', '78044', '6900 Main Place'),
493 | (493, 'New York City', 'New York', '10270', '361 Springs Pass'),
494 | (494, 'Prescott', 'Arizona', '86305', '829 Schlimgen Junction'),
495 | (495, 'Sandy', 'Utah', '84093', '09898 Dexter Place'),
496 | (496, 'Washington', 'District of Columbia', '20005', '19346 Mallard Hill'),
497 | (497, 'Baltimore', 'Maryland', '21281', '5 Starling Drive'),
498 | (498, 'Colorado Springs', 'Colorado', '80940', '49563 Sutherland Junction'),
499 | (499, 'Midland', 'Texas', '79710', '487 Monument Place'),
500 | (500, 'Sacramento', 'California', '95828', '0 Coolidge Trail');
501 |
502 | COMMIT;
503 |
504 | SELECT setval(pg_get_serial_sequence('address', 'id'),
505 | (select max(id) from "address"));
--------------------------------------------------------------------------------
/example/hasura/seeds/default/04_default_user_login_seeds.sql:
--------------------------------------------------------------------------------
1 | -- "password" bcrypted = $2y$10$4fLjiqiJ.Rh0F/qYfIfCeOy7a9nLJN49YzUEJbYnj2ZsiwVhGsOxK
2 | INSERT INTO public.user
3 | (name, email, password)
4 | VALUES
5 | ('Person', 'user@site.com', '$2y$10$4fLjiqiJ.Rh0F/qYfIfCeOy7a9nLJN49YzUEJbYnj2ZsiwVhGsOxK');
6 |
7 | INSERT INTO public.site_admin
8 | (name, email, password)
9 | VALUES
10 | ('Admin', 'admin@site.com', '$2y$10$4fLjiqiJ.Rh0F/qYfIfCeOy7a9nLJN49YzUEJbYnj2ZsiwVhGsOxK');
11 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@apollo/client": "^3.9.5",
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^13.4.0",
9 | "@testing-library/user-event": "^14.4.3",
10 | "@types/jest": "^29.2.4",
11 | "@types/node": "^18.11.17",
12 | "@types/react": "^18.0.26",
13 | "@types/react-dom": "^18.0.9",
14 | "graphql": "^16.6.0",
15 | "ra-data-hasura": "^0.5.4",
16 | "react": "^18.2.0",
17 | "react-admin": "^4.6.2",
18 | "react-dom": "^18.2.0",
19 | "react-scripts": "5.0.1",
20 | "typescript": "^4.9.4",
21 | "web-vitals": "^3.1.0"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/ra-data-hasura/20878f7c1352fd478bd82b86d9169ac9e5e4ea71/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/ra-data-hasura/20878f7c1352fd478bd82b86d9169ac9e5e4ea71/example/public/logo192.png
--------------------------------------------------------------------------------
/example/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/ra-data-hasura/20878f7c1352fd478bd82b86d9169ac9e5e4ea71/example/public/logo512.png
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/example/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/example/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Admin, Resource, DataProvider } from 'react-admin';
3 | import buildHasuraProvider from 'ra-data-hasura';
4 | import { ProductEdit, ProductList } from './product';
5 | import { createHttpLink } from '@apollo/client';
6 | import { setContext } from '@apollo/client/link/context';
7 | import { OrderEdit, OrderList } from './order';
8 | import { OrderProductList } from './order_product';
9 | import { UserEdit, UserList } from './user';
10 | import { AddressEdit, AddressList } from './address';
11 |
12 | const httpLink = createHttpLink({
13 | uri: 'http://localhost:8080/v1/graphql',
14 | });
15 |
16 | const authLink = setContext((_, { headers }) => {
17 | // return the headers to the context so httpLink can read them
18 | return {
19 | headers: {
20 | ...headers,
21 | authorization:
22 | // To generate your own JWT see Hasura Super App https://github.com/hasura/hasura-ecommerce
23 | 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsic2l0ZS1hZG1pbiIsInVzZXIiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoic2l0ZS1hZG1pbiIsIlgtSGFzdXJhLVNpdGUtQWRtaW4tSWQiOiIxIn0sImlhdCI6MTY1MTc4MTg1NH0.DsyJSVZYt7Ah8PIWFvnYbAvnpSDdMbhAkmjuLRE8Gas',
24 | },
25 | };
26 | });
27 |
28 | const App = () => {
29 | const [dataProvider, setDataProvider] = React.useState(
30 | null
31 | );
32 |
33 | React.useEffect(() => {
34 | const buildDataProvider = async () => {
35 | const dataProviderHasura = await buildHasuraProvider({
36 | clientOptions: {
37 | link: authLink.concat(httpLink) as any,
38 | },
39 | });
40 | setDataProvider(() => dataProviderHasura);
41 | };
42 | buildDataProvider();
43 | }, []);
44 |
45 | if (!dataProvider) return Loading...
;
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | };
57 |
58 | export default App;
59 |
--------------------------------------------------------------------------------
/example/src/address/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Datagrid,
3 | DateField,
4 | DateInput,
5 | Edit,
6 | List,
7 | ReferenceField,
8 | ReferenceInput,
9 | SelectInput,
10 | SimpleForm,
11 | TextField,
12 | TextInput,
13 | } from 'react-admin';
14 |
15 | export const AddressList = () => (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 |
34 | export const AddressEdit = () => (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 |
--------------------------------------------------------------------------------
/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/example/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const container = document.getElementById('root');
8 | const root = createRoot(container!); // createRoot(container!) if you use TypeScript
9 | root.render(
10 |
11 |
12 |
13 | );
14 |
15 | // If you want to start measuring performance in your app, pass a function
16 | // to log results (for example: reportWebVitals(console.log))
17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
18 | reportWebVitals();
19 |
--------------------------------------------------------------------------------
/example/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/src/order/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BooleanField,
3 | BooleanInput,
4 | Datagrid,
5 | DateField,
6 | DateInput,
7 | Edit,
8 | List,
9 | ReferenceField,
10 | ReferenceInput,
11 | SelectInput,
12 | SimpleForm,
13 | TextField,
14 | TextInput,
15 | } from 'react-admin';
16 |
17 | export const OrderList = () => (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 |
38 | export const OrderEdit = () => (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 |
--------------------------------------------------------------------------------
/example/src/order_product/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Datagrid,
3 | DateField,
4 | List,
5 | NumberField,
6 | ReferenceField,
7 | TextField,
8 | } from 'react-admin';
9 |
10 | export const OrderProductList = () => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 |
--------------------------------------------------------------------------------
/example/src/product/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BooleanInput,
3 | Datagrid,
4 | DateField,
5 | DateInput,
6 | Edit,
7 | List,
8 | NumberField,
9 | ReferenceInput,
10 | SelectInput,
11 | SimpleForm,
12 | TextField,
13 | TextInput,
14 | } from 'react-admin';
15 |
16 | export const ProductList = () => (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | export const ProductEdit = () => (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 |
--------------------------------------------------------------------------------
/example/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/example/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/example/src/user/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Datagrid,
3 | DateField,
4 | DateInput,
5 | Edit,
6 | EmailField,
7 | List,
8 | SimpleForm,
9 | TextField,
10 | TextInput,
11 | } from 'react-admin';
12 |
13 | export const UserList = () => (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | export const UserEdit = () => (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '^.+\\.tsx?$': 'ts-jest',
4 | },
5 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
6 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ra-data-hasura",
3 | "version": "0.7.1",
4 | "description": "A data provider for connecting react-admin to a Hasura endpoint",
5 | "main": "dist/index.js",
6 | "typings": "dist/index.d.ts",
7 | "sideEffects": false,
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/hasura/ra-data-hasura.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/hasura/ra-data-hasura/issues"
14 | },
15 | "homepage": "https://github.com/hasura/ra-data-hasura#readme",
16 | "authors": [
17 | "Praveen Durairaju",
18 | "Radcliffe Robinson"
19 | ],
20 | "keywords": [
21 | "reactjs",
22 | "react",
23 | "react-admin",
24 | "admin-on-rest",
25 | "rest",
26 | "graphql",
27 | "hasura"
28 | ],
29 | "license": "MIT",
30 | "scripts": {
31 | "build": "rimraf ./dist && tsc",
32 | "prepublishOnly": "npm run test && npm run build",
33 | "test": "jest",
34 | "prettier": "prettier --config ./.prettierrc --write '**/*.{js,jsx,md}'",
35 | "prepare": "husky install"
36 | },
37 | "dependencies": {
38 | "lodash": "^4.17.21",
39 | "ra-data-graphql": "^4.6.0 || ^5.0.3",
40 | "graphql": "^16.6.0"
41 | },
42 | "peerDependencies": {
43 | "ra-core": "^4.6.2 || ^5.0.0"
44 | },
45 | "devDependencies": {
46 | "@types/jest": "^29.5.2",
47 | "@types/lodash": "^4.14.191",
48 | "husky": "^8.0.2",
49 | "jest": "^29.5.0",
50 | "prettier": "~3.2.5",
51 | "pretty-quick": "^4.0.0",
52 | "rimraf": "^3.0.2",
53 | "ts-jest": "^29.1.0",
54 | "typescript": "^5.1.3"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/buildGqlQuery/buildArgs.ts:
--------------------------------------------------------------------------------
1 | import * as gqlTypes from '../graphql-ast-types-browser';
2 | import {
3 | IntrospectionField,
4 | ArgumentNode,
5 | VariableDefinitionNode,
6 | } from 'graphql';
7 | import {
8 | GET_LIST,
9 | GET_MANY,
10 | GET_MANY_REFERENCE,
11 | } from '../helpers/fetchActions';
12 | import getArgType from '../helpers/getArgType';
13 | import { FetchType } from '../types';
14 |
15 | export type BuildArgs = (
16 | query: IntrospectionField,
17 | variables: any
18 | ) => ArgumentNode[];
19 |
20 | export type BuildMetaArgs = (
21 | query: IntrospectionField,
22 | variables: any,
23 | aorFetchType: FetchType
24 | ) => ArgumentNode[];
25 |
26 | export type BuildApolloArgs = (
27 | query: IntrospectionField,
28 | variables: any
29 | ) => VariableDefinitionNode[];
30 |
31 | export const buildArgs: BuildArgs = (query, variables) => {
32 | if (query.args.length === 0) {
33 | return [];
34 | }
35 |
36 | const validVariables = Object.keys(variables).filter(
37 | (k) => typeof variables[k] !== 'undefined'
38 | );
39 |
40 | return query.args
41 | .filter((a) => validVariables.includes(a.name))
42 | .reduce(
43 | (acc, arg) => [
44 | ...acc,
45 | gqlTypes.argument(
46 | gqlTypes.name(arg.name),
47 | gqlTypes.variable(gqlTypes.name(arg.name))
48 | ),
49 | ],
50 | [] as ArgumentNode[]
51 | );
52 | };
53 |
54 | export const buildMetaArgs: BuildMetaArgs = (
55 | query,
56 | variables,
57 | aorFetchType
58 | ) => {
59 | if (query.args.length === 0) {
60 | return [];
61 | }
62 |
63 | const validVariables = Object.keys(variables).filter((k) => {
64 | if (
65 | aorFetchType === GET_LIST ||
66 | aorFetchType === GET_MANY ||
67 | aorFetchType === GET_MANY_REFERENCE
68 | ) {
69 | return (
70 | typeof variables[k] !== 'undefined' && k !== 'limit' && k !== 'offset'
71 | );
72 | }
73 |
74 | return typeof variables[k] !== 'undefined';
75 | });
76 |
77 | return query.args
78 | .filter((a) => validVariables.includes(a.name))
79 | .reduce(
80 | (acc, arg) => [
81 | ...acc,
82 | gqlTypes.argument(
83 | gqlTypes.name(arg.name),
84 | gqlTypes.variable(gqlTypes.name(arg.name))
85 | ),
86 | ],
87 | [] as ArgumentNode[]
88 | );
89 | };
90 |
91 | export const buildApolloArgs: BuildApolloArgs = (query, variables) => {
92 | if (query.args.length === 0) {
93 | return [];
94 | }
95 |
96 | const validVariables = Object.keys(variables).filter(
97 | (k) => typeof variables[k] !== 'undefined'
98 | );
99 |
100 | return query.args
101 | .filter((a) => validVariables.includes(a.name))
102 | .reduce((acc, arg) => {
103 | return [
104 | ...acc,
105 | gqlTypes.variableDefinition(
106 | gqlTypes.variable(gqlTypes.name(arg.name)),
107 | getArgType(arg)
108 | ),
109 | ];
110 | }, [] as VariableDefinitionNode[]);
111 | };
112 |
--------------------------------------------------------------------------------
/src/buildGqlQuery/buildFields.ts:
--------------------------------------------------------------------------------
1 | import { TypeKind, IntrospectionObjectType, FieldNode } from 'graphql';
2 | import * as gqlTypes from '../graphql-ast-types-browser';
3 | import getFinalType from '../helpers/getFinalType';
4 | import { FetchType } from '../types';
5 |
6 | export type BuildFields = (
7 | type: IntrospectionObjectType,
8 | aorFetchType?: FetchType
9 | ) => FieldNode[];
10 |
11 | export const buildFields: BuildFields = (type) =>
12 | type.fields.reduce((acc, field) => {
13 | const type = getFinalType(field.type);
14 |
15 | if (type.kind !== TypeKind.OBJECT && type.kind !== TypeKind.INTERFACE) {
16 | return [...acc, gqlTypes.field(gqlTypes.name(field.name))];
17 | }
18 |
19 | return acc;
20 | }, [] as FieldNode[]);
21 |
--------------------------------------------------------------------------------
/src/buildGqlQuery/index.ts:
--------------------------------------------------------------------------------
1 | import * as gqlTypes from '../graphql-ast-types-browser';
2 | import { IntrospectionField, OperationTypeNode } from 'graphql';
3 | import {
4 | GET_LIST,
5 | GET_MANY,
6 | GET_MANY_REFERENCE,
7 | DELETE,
8 | CREATE,
9 | UPDATE,
10 | UPDATE_MANY,
11 | DELETE_MANY,
12 | } from '../helpers/fetchActions';
13 | import { buildFields, BuildFields } from './buildFields';
14 | import {
15 | buildArgs,
16 | buildApolloArgs,
17 | buildMetaArgs,
18 | BuildArgs,
19 | BuildMetaArgs,
20 | BuildApolloArgs,
21 | } from './buildArgs';
22 | import { FetchType, IntrospectionResult } from '../types';
23 |
24 | export type BuildGqlQuery = (
25 | introspectionResults: IntrospectionResult,
26 | buildFields: BuildFields,
27 | buildMetaArgs: BuildMetaArgs,
28 | buildArgs: BuildArgs,
29 | buildApolloArgs: BuildApolloArgs,
30 | aggregateFieldName: (resourceName: string) => string
31 | ) => (
32 | resource: any,
33 | aorFetchType: FetchType,
34 | queryType: IntrospectionField,
35 | variables: any
36 | ) => any;
37 |
38 | export type BuildGqlQueryFactory = (
39 | introspectionResults: IntrospectionResult
40 | ) => ReturnType;
41 |
42 | export const buildGqlQuery: BuildGqlQuery =
43 | (
44 | _,
45 | buildFields,
46 | buildMetaArgs,
47 | buildArgs,
48 | buildApolloArgs,
49 | aggregateFieldName
50 | ) =>
51 | (resource, aorFetchType, queryType, variables) => {
52 | const { sortField, sortOrder, ...metaVariables } = variables;
53 | const apolloArgs = buildApolloArgs(queryType, variables);
54 | const args = buildArgs(queryType, variables);
55 | const metaArgs = buildMetaArgs(queryType, metaVariables, aorFetchType);
56 | const fields = buildFields(resource.type, aorFetchType);
57 | if (
58 | aorFetchType === GET_LIST ||
59 | aorFetchType === GET_MANY ||
60 | aorFetchType === GET_MANY_REFERENCE
61 | ) {
62 | let gqlArray = [
63 | gqlTypes.field(
64 | gqlTypes.name(queryType.name),
65 | gqlTypes.name('items'),
66 | args,
67 | null,
68 | gqlTypes.selectionSet(fields)
69 | ),
70 | ];
71 | // Skip aggregate calls when provided aggregateFieldName function returns NO_COUNT.
72 | // This is useful to avoid expensive count queries.
73 | if (aggregateFieldName(queryType.name) !== 'NO_COUNT') {
74 | gqlArray.push(
75 | gqlTypes.field(
76 | gqlTypes.name(aggregateFieldName(queryType.name)),
77 | gqlTypes.name('total'),
78 | metaArgs,
79 | null,
80 | gqlTypes.selectionSet([
81 | gqlTypes.field(
82 | gqlTypes.name('aggregate'),
83 | null,
84 | null,
85 | null,
86 | gqlTypes.selectionSet([gqlTypes.field(gqlTypes.name('count'))])
87 | ),
88 | ])
89 | )
90 | );
91 | }
92 | return gqlTypes.document([
93 | gqlTypes.operationDefinition(
94 | OperationTypeNode.QUERY,
95 | gqlTypes.selectionSet(gqlArray),
96 | gqlTypes.name(queryType.name),
97 | apolloArgs
98 | ),
99 | ]);
100 | }
101 |
102 | if (aorFetchType === UPDATE) {
103 | return gqlTypes.document([
104 | gqlTypes.operationDefinition(
105 | OperationTypeNode.MUTATION,
106 | gqlTypes.selectionSet([
107 | gqlTypes.field(
108 | gqlTypes.name(queryType.name),
109 | null,
110 | args,
111 | null,
112 | gqlTypes.selectionSet(fields)
113 | ),
114 | ]),
115 | gqlTypes.name(queryType.name),
116 | apolloArgs
117 | ),
118 | ]);
119 | }
120 |
121 | if (
122 | aorFetchType === CREATE ||
123 | aorFetchType === UPDATE_MANY ||
124 | aorFetchType === DELETE ||
125 | aorFetchType === DELETE_MANY
126 | ) {
127 | return gqlTypes.document([
128 | gqlTypes.operationDefinition(
129 | OperationTypeNode.MUTATION,
130 | gqlTypes.selectionSet([
131 | gqlTypes.field(
132 | gqlTypes.name(queryType.name),
133 | gqlTypes.name('data'),
134 | args,
135 | null,
136 | gqlTypes.selectionSet([
137 | gqlTypes.field(
138 | gqlTypes.name('returning'),
139 | null,
140 | null,
141 | null,
142 | gqlTypes.selectionSet(fields)
143 | ),
144 | ])
145 | ),
146 | ]),
147 | gqlTypes.name(queryType.name),
148 | apolloArgs
149 | ),
150 | ]);
151 | }
152 |
153 | return gqlTypes.document([
154 | gqlTypes.operationDefinition(
155 | OperationTypeNode.QUERY,
156 | gqlTypes.selectionSet([
157 | gqlTypes.field(
158 | gqlTypes.name(queryType.name),
159 | gqlTypes.name('returning'),
160 | args,
161 | null,
162 | gqlTypes.selectionSet(fields)
163 | ),
164 | ]),
165 | gqlTypes.name(queryType.name),
166 | apolloArgs
167 | ),
168 | ]);
169 | };
170 |
171 | const buildGqlQueryFactory: BuildGqlQueryFactory = (introspectionResults) =>
172 | buildGqlQuery(
173 | introspectionResults,
174 | buildFields,
175 | buildMetaArgs,
176 | buildArgs,
177 | buildApolloArgs,
178 | (resourceName) => `${resourceName}_aggregate`
179 | );
180 |
181 | export default buildGqlQueryFactory;
182 |
--------------------------------------------------------------------------------
/src/buildQuery/index.ts:
--------------------------------------------------------------------------------
1 | import { buildVariables } from '../buildVariables';
2 | import buildGqlQuery, { BuildGqlQueryFactory } from '../buildGqlQuery';
3 | import { getResponseParser, GetResponseParser } from '../getResponseParser';
4 | import type { FetchType, IntrospectionResult } from '../types';
5 |
6 | export type QueryResponse = {
7 | data: any;
8 | total?: number;
9 | pageInfo?: {
10 | hasNextPage?: boolean;
11 | hasPreviousPage?: boolean;
12 | };
13 | };
14 |
15 | export type BuildQuery = (introspectionResults: IntrospectionResult) => (
16 | aorFetchType: FetchType,
17 | resourceName: string,
18 | params: any
19 | ) => {
20 | query: any;
21 | variables: any;
22 | parseResponse: ({ data }: any) => QueryResponse;
23 | };
24 |
25 | export type BuildQueryFactory = (
26 | buildVariablesImpl: any,
27 | buildGqlQueryImpl: BuildGqlQueryFactory,
28 | getResponseParserImpl: GetResponseParser
29 | ) => BuildQuery;
30 |
31 | export const buildQueryFactory: BuildQueryFactory =
32 | (buildVariablesImpl, buildGqlQueryImpl, getResponseParserImpl) =>
33 | (introspectionResults) => {
34 | const knownResources = introspectionResults.resources.map(
35 | (r) => r.type.name
36 | );
37 |
38 | return (aorFetchType, resourceName, params) => {
39 | const resource = introspectionResults.resources.find(
40 | (r) => r.type.name === resourceName
41 | );
42 |
43 | if (!resource) {
44 | if (knownResources.length) {
45 | throw new Error(
46 | `Unknown resource ${resourceName}. Make sure it has been declared on your server side schema. Known resources are ${knownResources.join(
47 | ', '
48 | )}`
49 | );
50 | } else {
51 | throw new Error(
52 | `Unknown resource ${resourceName}. No resources were found. Make sure it has been declared on your server side schema and check if your Authorization header is properly set up.`
53 | );
54 | }
55 | }
56 |
57 | const queryType = resource[aorFetchType];
58 |
59 | if (!queryType) {
60 | throw new Error(
61 | `No query or mutation matching fetch type ${aorFetchType} could be found for resource ${resource.type.name}`
62 | );
63 | }
64 |
65 | const variables = buildVariablesImpl(introspectionResults)(
66 | resource,
67 | aorFetchType,
68 | params,
69 | queryType
70 | );
71 | const query = buildGqlQueryImpl(introspectionResults)(
72 | resource,
73 | aorFetchType,
74 | queryType,
75 | variables
76 | );
77 | const parseResponse = getResponseParserImpl(introspectionResults)(
78 | aorFetchType,
79 | resource
80 | );
81 |
82 | return {
83 | query,
84 | variables,
85 | parseResponse,
86 | };
87 | };
88 | };
89 |
90 | export default buildQueryFactory(
91 | buildVariables,
92 | buildGqlQuery,
93 | getResponseParser
94 | );
95 |
--------------------------------------------------------------------------------
/src/buildVariables/buildCreateVariables.ts:
--------------------------------------------------------------------------------
1 | import { typeAwareKeyValueReducer } from './typeAwareKeyValueReducer';
2 | import { FetchType, IntrospectedResource, IntrospectionResult } from '../types';
3 |
4 | type BuildCreateVariables = (
5 | introspectionResults: IntrospectionResult
6 | ) => (
7 | resource: IntrospectedResource,
8 | aorFetchType: FetchType,
9 | params: any,
10 | queryType: any
11 | ) => any;
12 |
13 | export const buildCreateVariables: BuildCreateVariables =
14 | (introspectionResults) => (resource, _, params, __) => {
15 | const reducer = typeAwareKeyValueReducer(
16 | introspectionResults,
17 | resource,
18 | params
19 | );
20 | return Object.keys(params.data).reduce(reducer, {});
21 | };
22 |
--------------------------------------------------------------------------------
/src/buildVariables/buildGetListVariables.ts:
--------------------------------------------------------------------------------
1 | import set from 'lodash/set';
2 | import omit from 'lodash/omit';
3 | import getFinalType from '../helpers/getFinalType';
4 | import type {
5 | FetchType,
6 | IntrospectionResult,
7 | IntrospectedResource,
8 | } from '../types';
9 |
10 | type BuildGetListVariables = (
11 | introspectionResults: IntrospectionResult
12 | ) => (
13 | resource: IntrospectedResource,
14 | aorFetchType: FetchType,
15 | params: any
16 | ) => any;
17 |
18 | const SPLIT_TOKEN = '#';
19 | const MULTI_SORT_TOKEN = ',';
20 | const SPLIT_OPERATION = '@';
21 |
22 | export const buildGetListVariables: BuildGetListVariables =
23 | () => (resource, _, params) => {
24 | const result: any = {};
25 | let { filter: filterObj = {} } = params;
26 | const { customFilters = [] } = params;
27 |
28 | const distinctOnField = 'distinct_on';
29 | /** Setting "distinct_on" to be the `filters` object attribute to be used inside RA
30 | * and setting to a `distinct_on` variable
31 | * and removing from the filter object
32 | */
33 | const { distinct_on = '' } = filterObj;
34 | filterObj = omit(filterObj, [distinctOnField]);
35 |
36 | /**
37 | * Nested entities are parsed by CRA, which returns a nested object
38 | * { 'level1': {'level2': 'test'}}
39 | * instead of { 'level1.level2': 'test'}
40 | * That's why we use a HASH for properties, when we declared nested stuff at CRA:
41 | * level1#level2@_ilike
42 | */
43 |
44 | /**
45 | keys with comma separated values
46 | {
47 | 'title@ilike,body@like,authors@similar': 'test',
48 | 'col1@like,col2@like': 'val'
49 | }
50 | */
51 | const orFilterKeys = Object.keys(filterObj).filter((e) => e.includes(','));
52 |
53 | /**
54 | format filters
55 | {
56 | 'title@ilike': 'test',
57 | 'body@like': 'test',
58 | 'authors@similar': 'test',
59 | 'col1@like': 'val',
60 | 'col2@like': 'val'
61 | }
62 | */
63 | const orFilterObj = orFilterKeys.reduce((acc, commaSeparatedKey) => {
64 | const keys = commaSeparatedKey.split(',');
65 | return {
66 | ...acc,
67 | ...keys.reduce((acc2, key) => {
68 | return {
69 | ...acc2,
70 | [key]: filterObj[commaSeparatedKey],
71 | };
72 | }, {}),
73 | };
74 | }, {});
75 | filterObj = omit(filterObj, orFilterKeys);
76 |
77 | const makeNestedFilter = (obj: any, operation: string): any => {
78 | if (Object.keys(obj).length === 1) {
79 | const [key] = Object.keys(obj);
80 | return { [key]: makeNestedFilter(obj[key], operation) };
81 | } else {
82 | return { [operation]: obj };
83 | }
84 | };
85 |
86 | const filterReducer = (obj: any) => (acc: any, key: any) => {
87 | let filter;
88 | if (key === 'ids') {
89 | filter = { id: { _in: obj['ids'] } };
90 | } else if (Array.isArray(obj[key])) {
91 | let [keyName, operation = '_in', opPath] = key.split(SPLIT_OPERATION);
92 | let value = opPath
93 | ? set({}, opPath.split(SPLIT_TOKEN), obj[key])
94 | : obj[key];
95 | filter = set({}, keyName.split(SPLIT_TOKEN), { [operation]: value });
96 | } else if (obj[key] && obj[key].format === 'hasura-raw-query') {
97 | filter = set({}, key.split(SPLIT_TOKEN), obj[key].value || {});
98 | } else {
99 | let [keyName, operation = ''] = key.split(SPLIT_OPERATION);
100 | let operator;
101 | if (operation === '{}') operator = {};
102 | const field = resource.type.fields.find((f) => f.name === keyName);
103 | if (field) {
104 | switch (getFinalType(field.type).name) {
105 | case 'String':
106 | operation = operation || '_ilike';
107 | if (!operator)
108 | operator = {
109 | [operation]: operation.includes('like')
110 | ? `%${obj[key]}%`
111 | : obj[key],
112 | };
113 | break;
114 | case 'jsonb':
115 | try {
116 | const parsedJSONQuery = JSON.parse(obj[key]);
117 | if (parsedJSONQuery) {
118 | operator = {
119 | [operation || '_contains']: parsedJSONQuery
120 | };
121 | }
122 | } catch (ex) {}
123 | break;
124 | default:
125 | if (!operator)
126 | operator = {
127 | [operation || '_eq']: operation.includes('like')
128 | ? `%${obj[key]}%`
129 | : obj[key],
130 | };
131 | }
132 | } else {
133 | // Else block runs when the field is not found in Graphql schema.
134 | // Most likely it's nested. If it's not, it's better to let
135 | // Hasura fail with a message than silently fail/ignore it
136 | if (!operator)
137 | operator = {
138 | [operation || '_eq']: operation.includes('like')
139 | ? `%${obj[key]}%`
140 | : obj[key],
141 | };
142 | }
143 | filter = set({}, keyName.split(SPLIT_TOKEN), operator);
144 | }
145 | return [...acc, filter];
146 | };
147 | const andFilters = Object.keys(filterObj)
148 | .reduce(filterReducer(filterObj), customFilters)
149 | .filter(Boolean);
150 | const orFilters = Object.keys(orFilterObj)
151 | .reduce(filterReducer(orFilterObj), [])
152 | .filter(Boolean);
153 |
154 | result['where'] = {
155 | _and: andFilters,
156 | ...(orFilters.length && { _or: orFilters }),
157 | };
158 |
159 | if (params.pagination && params.pagination.perPage > -1) {
160 | result['limit'] = parseInt(params.pagination.perPage, 10);
161 | result['offset'] =
162 | (params.pagination.page - 1) * params.pagination.perPage;
163 | }
164 |
165 | if (params.sort) {
166 | const { field, order } = params.sort;
167 | const hasMultiSort =
168 | field.includes(MULTI_SORT_TOKEN) || order.includes(MULTI_SORT_TOKEN);
169 | if (hasMultiSort) {
170 | const fields = field.split(MULTI_SORT_TOKEN);
171 | const orders = order
172 | .split(MULTI_SORT_TOKEN)
173 | .map((order: string) => order.toLowerCase());
174 |
175 | if (fields.length !== orders.length) {
176 | throw new Error(
177 | `The ${
178 | resource.type.name
179 | } list must have an order value for each sort field. Sort fields are "${fields.join(
180 | ','
181 | )}" but sort orders are "${orders.join(',')}"`
182 | );
183 | }
184 |
185 | const multiSort = fields.map((field: any, index: number) =>
186 | makeSort(field, orders[index])
187 | );
188 | result['order_by'] = multiSort;
189 | } else {
190 | result['order_by'] = makeSort(field, order);
191 | }
192 | }
193 |
194 | if (distinct_on) {
195 | result['distinct_on'] = distinct_on;
196 | }
197 |
198 | return result;
199 | };
200 |
201 | /**
202 | * if the field contains a SPLIT_OPERATION, it means it's column ordering option.
203 | *
204 | * @example
205 | * ```
206 | * makeSort('title', 'ASC') => { title: 'asc' }
207 | * ```
208 | * @example
209 | * ```
210 | * makeSort('title@nulls_last', 'ASC') => { title: 'asc_nulls_last' }
211 | * ```
212 | * @example
213 | * ```
214 | * makeSort('title@nulls_first', 'ASC') => { title: 'asc_nulls_first' }
215 | * ```
216 | *
217 | */
218 | const makeSort = (field: string, sort: 'ASC' | 'DESC') => {
219 | const [fieldName, operation] = field.split(SPLIT_OPERATION);
220 | const fieldSort = operation ? `${sort}_${operation}` : sort;
221 | return set({}, fieldName, fieldSort.toLowerCase());
222 | };
223 |
--------------------------------------------------------------------------------
/src/buildVariables/buildUpdateVariables.ts:
--------------------------------------------------------------------------------
1 | import isEqual from 'lodash/isEqual';
2 | import { IntrospectionInputObjectType } from 'graphql';
3 | import { FetchType, IntrospectedResource, IntrospectionResult } from '../types';
4 | import { typeAwareKeyValueReducer } from './typeAwareKeyValueReducer';
5 |
6 | type BuildUpdateVariables = (
7 | introspectionResults: IntrospectionResult
8 | ) => (
9 | resource: IntrospectedResource,
10 | aorFetchType: FetchType,
11 | params: any,
12 | queryType: any
13 | ) => any;
14 |
15 | export const buildUpdateVariables: BuildUpdateVariables =
16 | (introspectionResults) => (resource, _, params, __) => {
17 | const reducer = typeAwareKeyValueReducer(
18 | introspectionResults,
19 | resource,
20 | params
21 | );
22 | let permitted_fields: any = null;
23 | const resource_name = resource.type.name;
24 | if (resource_name) {
25 | let inputType = introspectionResults.types.find(
26 | (obj) => obj.name === `${resource_name}_set_input`
27 | );
28 | if (inputType) {
29 | let inputTypeFields = (inputType as IntrospectionInputObjectType)
30 | .inputFields;
31 | if (inputTypeFields) {
32 | permitted_fields = inputTypeFields.map((obj) => obj.name);
33 | }
34 | }
35 | }
36 | return Object.keys(params.data).reduce((acc, key) => {
37 | // If hasura permissions do not allow a field to be updated like (id),
38 | // we are not allowed to put it inside the variables
39 | // RA passes the whole previous Object here
40 | // https://github.com/marmelab/react-admin/issues/2414#issuecomment-428945402
41 |
42 | // Fetch permitted fields from *_set_input INPUT_OBJECT and filter out any key
43 | // not present inside it
44 | if (permitted_fields && !permitted_fields.includes(key)) return acc;
45 |
46 | if (
47 | params.previousData &&
48 | isEqual(params.data[key], params.previousData[key])
49 | ) {
50 | return acc;
51 | }
52 | return reducer(acc, key);
53 | }, {});
54 | };
55 |
--------------------------------------------------------------------------------
/src/buildVariables/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GET_ONE,
3 | GET_LIST,
4 | GET_MANY,
5 | GET_MANY_REFERENCE,
6 | DELETE,
7 | CREATE,
8 | UPDATE,
9 | UPDATE_MANY,
10 | DELETE_MANY,
11 | } from '../helpers/fetchActions';
12 | import { buildGetListVariables } from './buildGetListVariables';
13 | import { buildUpdateVariables } from './buildUpdateVariables';
14 | import { buildCreateVariables } from './buildCreateVariables';
15 | import { makeNestedTarget } from './makeNestedTarget';
16 | import { FetchType, IntrospectedResource, IntrospectionResult } from '../types';
17 |
18 | export type BuildVariables = (
19 | introspectionResults: IntrospectionResult
20 | ) => (
21 | resource: IntrospectedResource,
22 | aorFetchType: FetchType,
23 | params: any,
24 | queryType: any
25 | ) => any;
26 |
27 | export const buildVariables: BuildVariables =
28 | (introspectionResults) => (resource, aorFetchType, params, queryType) => {
29 | switch (aorFetchType) {
30 | case GET_LIST:
31 | return buildGetListVariables(introspectionResults)(
32 | resource,
33 | aorFetchType,
34 | params
35 | );
36 | case GET_MANY_REFERENCE: {
37 | var built = buildGetListVariables(introspectionResults)(
38 | resource,
39 | aorFetchType,
40 | params
41 | );
42 | if (params.filter) {
43 | return {
44 | ...built,
45 | where: {
46 | _and: [
47 | ...built['where']['_and'],
48 | makeNestedTarget(params.target, params.id),
49 | ],
50 | },
51 | };
52 | }
53 | return {
54 | ...built,
55 | where: makeNestedTarget(params.target, params.id),
56 | };
57 | }
58 | case GET_MANY:
59 | case DELETE_MANY:
60 | return {
61 | where: { id: { _in: params.ids } },
62 | };
63 |
64 | case GET_ONE:
65 | return {
66 | where: { id: { _eq: params.id } },
67 | limit: 1,
68 | };
69 |
70 | case DELETE:
71 | return {
72 | where: { id: { _eq: params.id } },
73 | };
74 | case CREATE:
75 | return {
76 | objects: buildCreateVariables(introspectionResults)(
77 | resource,
78 | aorFetchType,
79 | params,
80 | queryType
81 | ),
82 | };
83 |
84 | case UPDATE:
85 | return {
86 | _set: buildUpdateVariables(introspectionResults)(
87 | resource,
88 | aorFetchType,
89 | params,
90 | queryType
91 | ),
92 | pk_columns: { id: params.id },
93 | };
94 |
95 | case UPDATE_MANY:
96 | return {
97 | _set: buildUpdateVariables(introspectionResults)(
98 | resource,
99 | aorFetchType,
100 | params,
101 | queryType
102 | ),
103 | where: { id: { _in: params.ids } },
104 | };
105 | }
106 | };
107 |
--------------------------------------------------------------------------------
/src/buildVariables/makeNestedTarget.ts:
--------------------------------------------------------------------------------
1 | import set from 'lodash/set';
2 |
3 | type TargetEquals = {
4 | _eq: any;
5 | };
6 |
7 | type NestedTarget =
8 | | K
9 | | {
10 | [key: string]: K | NestedTarget;
11 | };
12 |
13 | export const makeNestedTarget = (
14 | target: string,
15 | id: string | number
16 | ): NestedTarget => {
17 | // This simple example should make clear what this function does
18 | // makeNestedTarget("a.b", 42)
19 | // makeNestedTarget("a#b", 42)
20 | // => { a: { b: { _eq: 42 } } }
21 | // makeNestedTarget("a#b@_contains@c#d", id)
22 | // => { a: { b: { _contains: { c: { d: 42 } } } } }
23 | // . -> # to make nested filtering support the same separator/standard
24 |
25 | let [path, operation = '_eq', oppath] = target.split('@');
26 | let value = oppath ? set({}, oppath
27 | .split(".").join("#") // nested filtering support the same standard
28 | .split('#'), id) : id;
29 |
30 | return set({}, path.split('.').join("#").split("#"), {
31 | [operation]: value,
32 | }) as NestedTarget;
33 | };
34 |
--------------------------------------------------------------------------------
/src/buildVariables/typeAwareKeyValueReducer.ts:
--------------------------------------------------------------------------------
1 | import type { IntrospectionField } from 'graphql';
2 | import { IntrospectionResult, IntrospectedResource } from '../types';
3 |
4 | /**
5 | * Returns a reducer that converts the react-admin key-values to hasura-acceptable values
6 | *
7 | * Currently that means that dates should never be an empty string, but in the future that can be extended
8 | * See https://github.com/marmelab/react-admin/pull/6199
9 | *
10 | */
11 |
12 | type TypeAwareKeyValueReducer = (
13 | introspectionResults: IntrospectionResult,
14 | resource: IntrospectedResource,
15 | params: any
16 | ) => (acc: any, key: any) => any;
17 |
18 | export const typeAwareKeyValueReducer: TypeAwareKeyValueReducer =
19 | (introspectionResults, resource, params) => (acc, key) => {
20 | const type = introspectionResults.types.find(
21 | (t) => t.name === resource.type.name
22 | );
23 |
24 | let value = params.data[key];
25 | if (type) {
26 | const field = (type as any)?.fields?.find(
27 | (t: IntrospectionField) => t.name === key
28 | );
29 | if (field?.type?.name === 'date' && params.data[key] === '') {
30 | value = null;
31 | }
32 | }
33 | return resource.type.fields.some((f) => f.name === key)
34 | ? {
35 | ...acc,
36 | [key]: value,
37 | }
38 | : acc;
39 | };
40 |
--------------------------------------------------------------------------------
/src/customDataProvider/index.ts:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import buildDataProvider, { Options } from 'ra-data-graphql';
3 | import {
4 | GET_ONE,
5 | GET_LIST,
6 | GET_MANY,
7 | GET_MANY_REFERENCE,
8 | DELETE,
9 | CREATE,
10 | UPDATE,
11 | UPDATE_MANY,
12 | DELETE_MANY,
13 | } from '../helpers/fetchActions';
14 | import {
15 | buildVariables as defaultBuildVariables,
16 | BuildVariables,
17 | } from '../buildVariables';
18 | import {
19 | getResponseParser as defaultGetResponseParser,
20 | GetResponseParser,
21 | } from '../getResponseParser';
22 | import { buildGqlQuery } from '../buildGqlQuery';
23 | import {
24 | buildMetaArgs,
25 | buildArgs,
26 | buildApolloArgs,
27 | BuildMetaArgs,
28 | BuildArgs,
29 | BuildApolloArgs,
30 | } from '../buildGqlQuery/buildArgs';
31 | import { buildFields, BuildFields } from '../buildGqlQuery/buildFields';
32 | import { buildQueryFactory } from '../buildQuery';
33 | import type { IntrospectionResult } from '../types';
34 |
35 | const defaultOptions: Partial = {
36 | introspection: {
37 | operationNames: {
38 | [GET_LIST]: (resource) => `${resource.name}`,
39 | [GET_ONE]: (resource) => `${resource.name}`,
40 | [GET_MANY]: (resource) => `${resource.name}`,
41 | [GET_MANY_REFERENCE]: (resource) => `${resource.name}`,
42 | [CREATE]: (resource) => `insert_${resource.name}`,
43 | [UPDATE]: (resource) => `update_${resource.name}_by_pk`,
44 | [UPDATE_MANY]: (resource) => `update_${resource.name}`,
45 | [DELETE]: (resource) => `delete_${resource.name}`,
46 | [DELETE_MANY]: (resource) => `delete_${resource.name}`,
47 | },
48 | },
49 | };
50 |
51 | const buildGqlQueryDefaults = {
52 | buildFields,
53 | buildMetaArgs,
54 | buildArgs,
55 | buildApolloArgs,
56 | aggregateFieldName: (resourceName: string) => `${resourceName}_aggregate`,
57 | };
58 |
59 | export type BuildCustomDataProvider = (
60 | options: Partial,
61 | buildGqlQueryOverrides?: {
62 | buildFields?: BuildFields;
63 | buildMetaArgs?: BuildMetaArgs;
64 | buildArgs?: BuildArgs;
65 | buildApolloArgs?: BuildApolloArgs;
66 | aggregateFieldName?: (resourceName: string) => string;
67 | },
68 | customBuildVariables?: BuildVariables,
69 | customGetResponseParser?: GetResponseParser
70 | ) => ReturnType;
71 |
72 | export const buildCustomDataProvider: BuildCustomDataProvider = (
73 | options = {},
74 | buildGqlQueryOverrides = {},
75 | customBuildVariables = defaultBuildVariables,
76 | customGetResponseParser = defaultGetResponseParser
77 | ) => {
78 | const buildGqlQueryOptions = {
79 | ...buildGqlQueryDefaults,
80 | ...buildGqlQueryOverrides,
81 | };
82 |
83 | const customBuildGqlQuery = (introspectionResults: IntrospectionResult) =>
84 | buildGqlQuery(
85 | introspectionResults,
86 | buildGqlQueryOptions.buildFields,
87 | buildGqlQueryOptions.buildMetaArgs,
88 | buildGqlQueryOptions.buildArgs,
89 | buildGqlQueryOptions.buildApolloArgs,
90 | buildGqlQueryOptions.aggregateFieldName
91 | );
92 |
93 | const buildQuery = buildQueryFactory(
94 | customBuildVariables,
95 | customBuildGqlQuery,
96 | customGetResponseParser
97 | );
98 |
99 | return buildDataProvider(merge({}, defaultOptions, { buildQuery }, options));
100 | };
101 |
--------------------------------------------------------------------------------
/src/getResponseParser/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GET_LIST,
3 | GET_MANY,
4 | GET_MANY_REFERENCE,
5 | GET_ONE,
6 | CREATE,
7 | UPDATE,
8 | DELETE,
9 | UPDATE_MANY,
10 | DELETE_MANY,
11 | } from '../helpers/fetchActions';
12 | import { IntrospectionResult, IntrospectedResource, FetchType } from '../types';
13 | import { QueryResponse } from '../buildQuery';
14 | import { sanitizeResource } from './sanitizeResource';
15 |
16 | export type GetResponseParser = (
17 | introspectionResults: IntrospectionResult
18 | ) => (
19 | aorFetchType: FetchType,
20 | resource?: IntrospectedResource
21 | ) => (res: { data: any }) => QueryResponse;
22 |
23 | export const getResponseParser: GetResponseParser =
24 | () => (aorFetchType, resource) => (res) => {
25 | const response = res.data;
26 |
27 | switch (aorFetchType) {
28 | case GET_MANY_REFERENCE:
29 | case GET_LIST:
30 | let output: QueryResponse = {
31 | data: response.items.map(sanitizeResource),
32 | };
33 | if (typeof response.total !== 'undefined') {
34 | output.total = response.total.aggregate.count;
35 | } else {
36 | // TODO: behave smarter and set hasNextPage=false when no more records exist.
37 | output.pageInfo = {
38 | hasPreviousPage: true,
39 | hasNextPage: true,
40 | };
41 | }
42 | return output;
43 |
44 | case GET_MANY:
45 | return { data: response.items.map(sanitizeResource) };
46 |
47 | case GET_ONE:
48 | return { data: sanitizeResource(response.returning[0]) };
49 |
50 | case CREATE:
51 | case DELETE:
52 | return { data: sanitizeResource(response.data.returning[0]) };
53 |
54 | case UPDATE:
55 | return { data: sanitizeResource(response[resource!.UPDATE.name]) };
56 | case UPDATE_MANY:
57 | case DELETE_MANY:
58 | return { data: response.data.returning.map((x: { id: any }) => x.id) };
59 |
60 | default:
61 | throw Error(`Expected a proper fetchType, got: ${aorFetchType}`);
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/src/getResponseParser/sanitizeResource.test.ts:
--------------------------------------------------------------------------------
1 | import { sanitizeResource } from './sanitizeResource';
2 |
3 | describe('sanitizeResource', () => {
4 | it('should properly parse array values at any depth', () => {
5 | expect(sanitizeResource([0, 1, 2, 3])).toEqual([0, 1, 2, 3]);
6 | expect(sanitizeResource([[0, 1, 2, 3]])).toEqual([[0, 1, 2, 3]]);
7 | expect(sanitizeResource({ data: [[0, 1, 2, 3]] })).toEqual({
8 | data: [[0, 1, 2, 3]],
9 | });
10 | });
11 |
12 | it('should add keyIds for arrays of objects with id', () => {
13 | expect(
14 | sanitizeResource({
15 | testValue: [
16 | { id: 1, name: 'Test 1' },
17 | { id: 2, name: 'Test 2' },
18 | ],
19 | })
20 | ).toEqual({
21 | testValue: [
22 | { id: 1, name: 'Test 1' },
23 | { id: 2, name: 'Test 2' },
24 | ],
25 | testValueIds: [1, 2],
26 | });
27 | });
28 |
29 | it('should add key.id value for objects with id', () => {
30 | expect(
31 | sanitizeResource({
32 | testValue: { id: 1, name: 'Test 1' },
33 | })
34 | ).toEqual({
35 | testValue: { id: 1, name: 'Test 1' },
36 | 'testValue.id': 1,
37 | });
38 | });
39 |
40 | it('It keeps null values', () => {
41 | expect(
42 | sanitizeResource([
43 | { name: 'vendor', value: 'Test Vendor' },
44 | { name: 'brand', value: null },
45 | ])
46 | ).toEqual([
47 | { name: 'vendor', value: 'Test Vendor' },
48 | { name: 'brand', value: null },
49 | ]);
50 | });
51 |
52 | it('Should skip key with two underscores', () => {
53 | expect(
54 | sanitizeResource([
55 | {
56 | name: 'vendor',
57 | value: 'Test Vendor',
58 | metadata: {
59 | _key: 'value',
60 | fields: [{ _type: 'string', name: 'title' }],
61 | },
62 | __typename: 'name',
63 | },
64 | ])
65 | ).toEqual([
66 | {
67 | name: 'vendor',
68 | value: 'Test Vendor',
69 | metadata: {
70 | _key: 'value',
71 | fields: [{ _type: 'string', name: 'title' }],
72 | },
73 | },
74 | ]);
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/src/getResponseParser/sanitizeResource.ts:
--------------------------------------------------------------------------------
1 | function isPrimitive(test: any) {
2 | return test !== Object(test);
3 | }
4 | export const sanitizeResource = (data: any): any => {
5 | // primitive no transformation needed (catches null, undefined, string, number, boolean)
6 | if (isPrimitive(data)) {
7 | return data;
8 | }
9 |
10 | // array, apply sanitizeResource to each element
11 | if (Array.isArray(data)) {
12 | return data.map(sanitizeResource);
13 | }
14 |
15 | // default object, check each (key, value) pair
16 | return Object.entries(data).reduce((acc, [key, value]) => {
17 | // intend to remove the following reserved names https://spec.graphql.org/draft/#sec-Names.Reserved-Names
18 | if (key.startsWith('__')) {
19 | return acc;
20 | }
21 |
22 | const newAcc: Record = { ...acc };
23 |
24 | // if it's an array of objects, we want to create a new key with the list of ids
25 | if (Array.isArray(value) && value?.[0]?.id && value?.[0]?.id !== null) {
26 | newAcc[`${key}Ids`] = value.map((d) => d.id);
27 | }
28 |
29 | // if it's an object with an id, we want to create a new key with the id
30 | if ((value as any)?.id) {
31 | newAcc[`${key}.id`] = (value as any).id;
32 | }
33 |
34 | return { ...newAcc, [key]: sanitizeResource(value) };
35 | }, {} as Record);
36 | };
37 |
--------------------------------------------------------------------------------
/src/graphql-ast-types-browser/definitions/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true,
5 | });
6 |
7 | var _typeof =
8 | typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
9 | ? function (obj) {
10 | return typeof obj;
11 | }
12 | : function (obj) {
13 | return obj &&
14 | typeof Symbol === 'function' &&
15 | obj.constructor === Symbol &&
16 | obj !== Symbol.prototype
17 | ? 'symbol'
18 | : typeof obj;
19 | };
20 |
21 | exports.default = defineType;
22 | exports.chain = chain;
23 | exports.assertEach = assertEach;
24 | exports.assertOneOf = assertOneOf;
25 | exports.assertNodeType = assertNodeType;
26 | exports.assertNodeOrValueType = assertNodeOrValueType;
27 | exports.assertValueType = assertValueType;
28 | exports.assertArrayOf = assertArrayOf;
29 | var t = require('../index');
30 |
31 | var BUILDER_KEYS = (exports.BUILDER_KEYS = {});
32 | var NODE_FIELDS = (exports.NODE_FIELDS = {});
33 | var ALIAS_KEYS = (exports.ALIAS_KEYS = {});
34 |
35 | /**
36 | * Used to define an AST node.
37 | * @param {String} type The AST node name
38 | * @param {Object} opts Type definition object
39 | * @returns {void}
40 | */
41 | function defineType(type) {
42 | var _ref =
43 | arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
44 | _ref$fields = _ref.fields,
45 | fields = _ref$fields === undefined ? {} : _ref$fields,
46 | _ref$aliases = _ref.aliases,
47 | aliases = _ref$aliases === undefined ? [] : _ref$aliases,
48 | _ref$builder = _ref.builder,
49 | builder = _ref$builder === undefined ? [] : _ref$builder;
50 |
51 | for (var key in fields) {
52 | var field = fields[key];
53 |
54 | // Sets field as optional if builder exist but validator does not.
55 | if (builder.indexOf(key) === -1) {
56 | field.optional = true;
57 | }
58 | }
59 |
60 | BUILDER_KEYS[type] = builder;
61 | NODE_FIELDS[type] = fields;
62 | ALIAS_KEYS[type] = aliases;
63 | }
64 |
65 | function getType(val) {
66 | if (Array.isArray(val)) {
67 | return 'array';
68 | } else if (val === null) {
69 | return 'null';
70 | } else if (val === undefined) {
71 | return 'undefined';
72 | } else {
73 | return typeof val === 'undefined' ? 'undefined' : _typeof(val);
74 | }
75 | }
76 |
77 | // Validation helpers
78 |
79 | function chain() {
80 | for (
81 | var _len = arguments.length, fns = Array(_len), _key = 0;
82 | _key < _len;
83 | _key++
84 | ) {
85 | fns[_key] = arguments[_key];
86 | }
87 |
88 | return function validate() {
89 | for (
90 | var _len2 = arguments.length, args = Array(_len2), _key2 = 0;
91 | _key2 < _len2;
92 | _key2++
93 | ) {
94 | args[_key2] = arguments[_key2];
95 | }
96 |
97 | fns.forEach(function (fn) {
98 | return fn.apply(undefined, args);
99 | });
100 | };
101 | }
102 |
103 | function assertEach(callback) {
104 | function validator(node, key, val) {
105 | if (!Array.isArray(val)) {
106 | return;
107 | }
108 |
109 | val.forEach(function (it, i) {
110 | return callback(node, key + '[' + i + ']', it);
111 | });
112 | }
113 | return validator;
114 | }
115 |
116 | function assertOneOf() {
117 | for (
118 | var _len3 = arguments.length, vals = Array(_len3), _key3 = 0;
119 | _key3 < _len3;
120 | _key3++
121 | ) {
122 | vals[_key3] = arguments[_key3];
123 | }
124 |
125 | function validate(node, key, val) {
126 | if (vals.indexOf(val.kind) < 0) {
127 | throw new TypeError(
128 | 'Property ' +
129 | key +
130 | ' expected value to be one of ' +
131 | JSON.stringify(vals) +
132 | ' but got ' +
133 | JSON.stringify(val)
134 | );
135 | }
136 | }
137 |
138 | return validate;
139 | }
140 |
141 | function assertNodeType() {
142 | for (
143 | var _len4 = arguments.length, types = Array(_len4), _key4 = 0;
144 | _key4 < _len4;
145 | _key4++
146 | ) {
147 | types[_key4] = arguments[_key4];
148 | }
149 |
150 | function validate(node, key, val) {
151 | var valid = types.every(function (type) {
152 | return t.is(type, val);
153 | });
154 |
155 | if (!valid) {
156 | throw new TypeError(
157 | 'Property ' +
158 | key +
159 | ' of ' +
160 | node.type +
161 | ' expected node to be of a type ' +
162 | JSON.stringify(types) +
163 | ' ' +
164 | ('but instead got ' + JSON.stringify(val && val.type))
165 | );
166 | }
167 | }
168 |
169 | return validate;
170 | }
171 |
172 | function assertNodeOrValueType() {
173 | for (
174 | var _len5 = arguments.length, types = Array(_len5), _key5 = 0;
175 | _key5 < _len5;
176 | _key5++
177 | ) {
178 | types[_key5] = arguments[_key5];
179 | }
180 |
181 | function validate(node, key, val) {
182 | var valid = types.every(function (type) {
183 | return getType(val) === type || t.is(type, val);
184 | });
185 |
186 | if (!valid) {
187 | throw new TypeError(
188 | 'Property ' +
189 | key +
190 | ' of ' +
191 | node.type +
192 | ' expected node to be of a type ' +
193 | JSON.stringify(types) +
194 | ' ' +
195 | ('but instead got ' + JSON.stringify(val && val.type))
196 | );
197 | }
198 | }
199 |
200 | return validate;
201 | }
202 |
203 | function assertValueType(type) {
204 | function validate(node, key, val) {
205 | var valid = getType(val) === type;
206 |
207 | if (!valid) {
208 | throw new TypeError(
209 | 'Property ' +
210 | key +
211 | ' expected type of ' +
212 | type +
213 | ' but got ' +
214 | getType(val)
215 | );
216 | }
217 | }
218 |
219 | return validate;
220 | }
221 |
222 | function assertArrayOf(cb) {
223 | return chain(assertValueType('array'), assertEach(cb));
224 | }
225 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kZWZpbml0aW9ucy9pbmRleC5qcyJdLCJuYW1lcyI6WyJkZWZpbmVUeXBlIiwiY2hhaW4iLCJhc3NlcnRFYWNoIiwiYXNzZXJ0T25lT2YiLCJhc3NlcnROb2RlVHlwZSIsImFzc2VydE5vZGVPclZhbHVlVHlwZSIsImFzc2VydFZhbHVlVHlwZSIsImFzc2VydEFycmF5T2YiLCJ0IiwicmVxdWlyZSIsIkJVSUxERVJfS0VZUyIsIk5PREVfRklFTERTIiwiQUxJQVNfS0VZUyIsInR5cGUiLCJmaWVsZHMiLCJhbGlhc2VzIiwiYnVpbGRlciIsImtleSIsImZpZWxkIiwiaW5kZXhPZiIsIm9wdGlvbmFsIiwiZ2V0VHlwZSIsInZhbCIsIkFycmF5IiwiaXNBcnJheSIsInVuZGVmaW5lZCIsImZucyIsInZhbGlkYXRlIiwiYXJncyIsImZvckVhY2giLCJmbiIsImNhbGxiYWNrIiwidmFsaWRhdG9yIiwibm9kZSIsIml0IiwiaSIsInZhbHMiLCJraW5kIiwiVHlwZUVycm9yIiwiSlNPTiIsInN0cmluZ2lmeSIsInR5cGVzIiwidmFsaWQiLCJldmVyeSIsImlzIiwiY2IiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O2tCQTBCd0JBLFU7UUFnQ1JDLEssR0FBQUEsSztRQU1BQyxVLEdBQUFBLFU7UUFXQUMsVyxHQUFBQSxXO1FBY0FDLGMsR0FBQUEsYztRQWdCQUMscUIsR0FBQUEscUI7UUFnQkFDLGUsR0FBQUEsZTtRQWNBQyxhLEdBQUFBLGE7QUF0SWhCLElBQU1DLElBQUlDLFFBQVEsVUFBUixDQUFWOztBQU9PLElBQU1DLHNDQUFrRCxFQUF4RDtBQUNBLElBQU1DLG9DQUVULEVBRkc7QUFHQSxJQUFNQyxrQ0FBZ0QsRUFBdEQ7O0FBUVA7Ozs7OztBQU1lLFNBQVNaLFVBQVQsQ0FDYmEsSUFEYSxFQUdiO0FBQUEsaUZBRHNELEVBQ3REO0FBQUEseUJBREVDLE1BQ0Y7QUFBQSxNQURFQSxNQUNGLCtCQURXLEVBQ1g7QUFBQSwwQkFEZUMsT0FDZjtBQUFBLE1BRGVBLE9BQ2YsZ0NBRHlCLEVBQ3pCO0FBQUEsMEJBRDZCQyxPQUM3QjtBQUFBLE1BRDZCQSxPQUM3QixnQ0FEdUMsRUFDdkM7O0FBQ0EsT0FBSyxJQUFNQyxHQUFYLElBQWtCSCxNQUFsQixFQUEwQjtBQUN4QixRQUFNSSxRQUFRSixPQUFPRyxHQUFQLENBQWQ7O0FBRUE7QUFDQSxRQUFJRCxRQUFRRyxPQUFSLENBQWdCRixHQUFoQixNQUF5QixDQUFDLENBQTlCLEVBQWlDO0FBQy9CQyxZQUFNRSxRQUFOLEdBQWlCLElBQWpCO0FBQ0Q7QUFDRjs7QUFFRFYsZUFBYUcsSUFBYixJQUFxQkcsT0FBckI7QUFDQUwsY0FBWUUsSUFBWixJQUFvQkMsTUFBcEI7QUFDQUYsYUFBV0MsSUFBWCxJQUFtQkUsT0FBbkI7QUFDRDs7QUFFRCxTQUFTTSxPQUFULENBQWlCQyxHQUFqQixFQUFzQjtBQUNwQixNQUFJQyxNQUFNQyxPQUFOLENBQWNGLEdBQWQsQ0FBSixFQUF3QjtBQUN0QixXQUFPLE9BQVA7QUFDRCxHQUZELE1BRU8sSUFBSUEsUUFBUSxJQUFaLEVBQWtCO0FBQ3ZCLFdBQU8sTUFBUDtBQUNELEdBRk0sTUFFQSxJQUFJQSxRQUFRRyxTQUFaLEVBQXVCO0FBQzVCLFdBQU8sV0FBUDtBQUNELEdBRk0sTUFFQTtBQUNMLGtCQUFjSCxHQUFkLHlDQUFjQSxHQUFkO0FBQ0Q7QUFDRjs7QUFFRDs7QUFFTyxTQUFTckIsS0FBVCxHQUFrRDtBQUFBLG9DQUFoQ3lCLEdBQWdDO0FBQWhDQSxPQUFnQztBQUFBOztBQUN2RCxTQUFPLFNBQVNDLFFBQVQsR0FBMkI7QUFBQSx1Q0FBTkMsSUFBTTtBQUFOQSxVQUFNO0FBQUE7O0FBQ2hDRixRQUFJRyxPQUFKLENBQVk7QUFBQSxhQUFNQyxvQkFBTUYsSUFBTixDQUFOO0FBQUEsS0FBWjtBQUNELEdBRkQ7QUFHRDs7QUFFTSxTQUFTMUIsVUFBVCxDQUFvQjZCLFFBQXBCLEVBQWtEO0FBQ3ZELFdBQVNDLFNBQVQsQ0FBbUJDLElBQW5CLEVBQXlCaEIsR0FBekIsRUFBOEJLLEdBQTlCLEVBQW1DO0FBQ2pDLFFBQUksQ0FBQ0MsTUFBTUMsT0FBTixDQUFjRixHQUFkLENBQUwsRUFBeUI7QUFDdkI7QUFDRDs7QUFFREEsUUFBSU8sT0FBSixDQUFZLFVBQUNLLEVBQUQsRUFBS0MsQ0FBTDtBQUFBLGFBQVdKLFNBQVNFLElBQVQsRUFBa0JoQixHQUFsQixTQUF5QmtCLENBQXpCLFFBQStCRCxFQUEvQixDQUFYO0FBQUEsS0FBWjtBQUNEO0FBQ0QsU0FBT0YsU0FBUDtBQUNEOztBQUVNLFNBQVM3QixXQUFULEdBQXVEO0FBQUEscUNBQS9CaUMsSUFBK0I7QUFBL0JBLFFBQStCO0FBQUE7O0FBQzVELFdBQVNULFFBQVQsQ0FBa0JNLElBQWxCLEVBQXdCaEIsR0FBeEIsRUFBNkJLLEdBQTdCLEVBQWtDO0FBQ2hDLFFBQUljLEtBQUtqQixPQUFMLENBQWFHLElBQUllLElBQWpCLElBQXlCLENBQTdCLEVBQWdDO0FBQzlCLFlBQU0sSUFBSUMsU0FBSixlQUNRckIsR0FEUixxQ0FDMkNzQixLQUFLQyxTQUFMLENBQzdDSixJQUQ2QyxDQUQzQyxpQkFHU0csS0FBS0MsU0FBTCxDQUFlbEIsR0FBZixDQUhULENBQU47QUFLRDtBQUNGOztBQUVELFNBQU9LLFFBQVA7QUFDRDs7QUFFTSxTQUFTdkIsY0FBVCxHQUEyRDtBQUFBLHFDQUFoQ3FDLEtBQWdDO0FBQWhDQSxTQUFnQztBQUFBOztBQUNoRSxXQUFTZCxRQUFULENBQWtCTSxJQUFsQixFQUF3QmhCLEdBQXhCLEVBQTZCSyxHQUE3QixFQUFrQztBQUNoQyxRQUFNb0IsUUFBUUQsTUFBTUUsS0FBTixDQUFZO0FBQUEsYUFBUW5DLEVBQUVvQyxFQUFGLENBQUsvQixJQUFMLEVBQVdTLEdBQVgsQ0FBUjtBQUFBLEtBQVosQ0FBZDs7QUFFQSxRQUFJLENBQUNvQixLQUFMLEVBQVk7QUFDVixZQUFNLElBQUlKLFNBQUosQ0FDSixjQUFZckIsR0FBWixZQUFzQmdCLEtBQUtwQixJQUEzQix1Q0FBaUUwQixLQUFLQyxTQUFMLENBQy9EQyxLQUQrRCxDQUFqRSwrQkFFMEJGLEtBQUtDLFNBQUwsQ0FBZWxCLE9BQU9BLElBQUlULElBQTFCLENBRjFCLENBREksQ0FBTjtBQUtEO0FBQ0Y7O0FBRUQsU0FBT2MsUUFBUDtBQUNEOztBQUVNLFNBQVN0QixxQkFBVCxHQUFrRTtBQUFBLHFDQUFoQ29DLEtBQWdDO0FBQWhDQSxTQUFnQztBQUFBOztBQUN2RSxXQUFTZCxRQUFULENBQWtCTSxJQUFsQixFQUF3QmhCLEdBQXhCLEVBQTZCSyxHQUE3QixFQUFrQztBQUNoQyxRQUFNb0IsUUFBUUQsTUFBTUUsS0FBTixDQUFZO0FBQUEsYUFBUXRCLFFBQVFDLEdBQVIsTUFBaUJULElBQWpCLElBQXlCTCxFQUFFb0MsRUFBRixDQUFLL0IsSUFBTCxFQUFXUyxHQUFYLENBQWpDO0FBQUEsS0FBWixDQUFkOztBQUVBLFFBQUksQ0FBQ29CLEtBQUwsRUFBWTtBQUNWLFlBQU0sSUFBSUosU0FBSixDQUNKLGNBQVlyQixHQUFaLFlBQXNCZ0IsS0FBS3BCLElBQTNCLHVDQUFpRTBCLEtBQUtDLFNBQUwsQ0FDL0RDLEtBRCtELENBQWpFLCtCQUUwQkYsS0FBS0MsU0FBTCxDQUFlbEIsT0FBT0EsSUFBSVQsSUFBMUIsQ0FGMUIsQ0FESSxDQUFOO0FBS0Q7QUFDRjs7QUFFRCxTQUFPYyxRQUFQO0FBQ0Q7O0FBRU0sU0FBU3JCLGVBQVQsQ0FBeUJPLElBQXpCLEVBQWlEO0FBQ3RELFdBQVNjLFFBQVQsQ0FBa0JNLElBQWxCLEVBQXdCaEIsR0FBeEIsRUFBNkJLLEdBQTdCLEVBQWtDO0FBQ2hDLFFBQU1vQixRQUFRckIsUUFBUUMsR0FBUixNQUFpQlQsSUFBL0I7O0FBRUEsUUFBSSxDQUFDNkIsS0FBTCxFQUFZO0FBQ1YsWUFBTSxJQUFJSixTQUFKLGVBQ1FyQixHQURSLDBCQUNnQ0osSUFEaEMsaUJBQ2dEUSxRQUFRQyxHQUFSLENBRGhELENBQU47QUFHRDtBQUNGOztBQUVELFNBQU9LLFFBQVA7QUFDRDs7QUFFTSxTQUFTcEIsYUFBVCxDQUF1QnNDLEVBQXZCLEVBQStDO0FBQ3BELFNBQU81QyxNQUFNSyxnQkFBZ0IsT0FBaEIsQ0FBTixFQUFnQ0osV0FBVzJDLEVBQVgsQ0FBaEMsQ0FBUDtBQUNEIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQGZsb3dcbmNvbnN0IHQgPSByZXF1aXJlKFwiLi4vaW5kZXhcIik7XG5cbnR5cGUgVmFsaWRhdG9yID0ge1xuICB2YWxpZGF0ZTogRnVuY3Rpb24sXG4gIG9wdGlvbmFsPzogYm9vbGVhblxufTtcblxuZXhwb3J0IGNvbnN0IEJVSUxERVJfS0VZUzogeyBbdHlwZTogc3RyaW5nXTogQXJyYXk8c3RyaW5nPiB9ID0ge307XG5leHBvcnQgY29uc3QgTk9ERV9GSUVMRFM6IHtcbiAgW3R5cGU6IHN0cmluZ106IHsgW2ZpZWxkS2V5OiBzdHJpbmddOiBWYWxpZGF0b3IgfVxufSA9IHt9O1xuZXhwb3J0IGNvbnN0IEFMSUFTX0tFWVM6IHsgW3R5cGU6IHN0cmluZ106IEFycmF5PHN0cmluZz4gfSA9IHt9O1xuXG50eXBlIE9wdGlvbiA9IHtcbiAgZmllbGRzPzogeyBbZmllbGRLZXk6IHN0cmluZ106IFZhbGlkYXRvciB9LFxuICBhbGlhc2VzPzogQXJyYXk8c3RyaW5nPixcbiAgYnVpbGRlcj86IEFycmF5PHN0cmluZz4gLy8gTm9kZSBwcm9wZXJ0aWVzIHRvIGJlIHRyYW5zZm9ybWVkIGludG8gcGFyYW1zXG59O1xuXG4vKipcbiAqIFVzZWQgdG8gZGVmaW5lIGFuIEFTVCBub2RlLlxuICogQHBhcmFtIHtTdHJpbmd9IHR5cGUgVGhlIEFTVCBub2RlIG5hbWVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvcHRzIFR5cGUgZGVmaW5pdGlvbiBvYmplY3RcbiAqIEByZXR1cm5zIHt2b2lkfVxuICovXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBkZWZpbmVUeXBlKFxuICB0eXBlOiBzdHJpbmcsXG4gIHsgZmllbGRzID0ge30sIGFsaWFzZXMgPSBbXSwgYnVpbGRlciA9IFtdIH06IE9wdGlvbiA9IHt9XG4pIHtcbiAgZm9yIChjb25zdCBrZXkgaW4gZmllbGRzKSB7XG4gICAgY29uc3QgZmllbGQgPSBmaWVsZHNba2V5XTtcblxuICAgIC8vIFNldHMgZmllbGQgYXMgb3B0aW9uYWwgaWYgYnVpbGRlciBleGlzdCBidXQgdmFsaWRhdG9yIGRvZXMgbm90LlxuICAgIGlmIChidWlsZGVyLmluZGV4T2Yoa2V5KSA9PT0gLTEpIHtcbiAgICAgIGZpZWxkLm9wdGlvbmFsID0gdHJ1ZTtcbiAgICB9XG4gIH1cblxuICBCVUlMREVSX0tFWVNbdHlwZV0gPSBidWlsZGVyO1xuICBOT0RFX0ZJRUxEU1t0eXBlXSA9IGZpZWxkcztcbiAgQUxJQVNfS0VZU1t0eXBlXSA9IGFsaWFzZXM7XG59XG5cbmZ1bmN0aW9uIGdldFR5cGUodmFsKSB7XG4gIGlmIChBcnJheS5pc0FycmF5KHZhbCkpIHtcbiAgICByZXR1cm4gXCJhcnJheVwiO1xuICB9IGVsc2UgaWYgKHZhbCA9PT0gbnVsbCkge1xuICAgIHJldHVybiBcIm51bGxcIjtcbiAgfSBlbHNlIGlmICh2YWwgPT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybiBcInVuZGVmaW5lZFwiO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiB0eXBlb2YgdmFsO1xuICB9XG59XG5cbi8vIFZhbGlkYXRpb24gaGVscGVyc1xuXG5leHBvcnQgZnVuY3Rpb24gY2hhaW4oLi4uZm5zOiBBcnJheTxGdW5jdGlvbj4pOiBGdW5jdGlvbiB7XG4gIHJldHVybiBmdW5jdGlvbiB2YWxpZGF0ZSguLi5hcmdzKSB7XG4gICAgZm5zLmZvckVhY2goZm4gPT4gZm4oLi4uYXJncykpO1xuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0RWFjaChjYWxsYmFjazogRnVuY3Rpb24pOiBGdW5jdGlvbiB7XG4gIGZ1bmN0aW9uIHZhbGlkYXRvcihub2RlLCBrZXksIHZhbCkge1xuICAgIGlmICghQXJyYXkuaXNBcnJheSh2YWwpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdmFsLmZvckVhY2goKGl0LCBpKSA9PiBjYWxsYmFjayhub2RlLCBgJHtrZXl9WyR7aX1dYCwgaXQpKTtcbiAgfVxuICByZXR1cm4gdmFsaWRhdG9yO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0T25lT2YoLi4udmFsczogQXJyYXk8c3RyaW5nPik6IEZ1bmN0aW9uIHtcbiAgZnVuY3Rpb24gdmFsaWRhdGUobm9kZSwga2V5LCB2YWwpIHtcbiAgICBpZiAodmFscy5pbmRleE9mKHZhbC5raW5kKSA8IDApIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXG4gICAgICAgIGBQcm9wZXJ0eSAke2tleX0gZXhwZWN0ZWQgdmFsdWUgdG8gYmUgb25lIG9mICR7SlNPTi5zdHJpbmdpZnkoXG4gICAgICAgICAgdmFsc1xuICAgICAgICApfSBidXQgZ290ICR7SlNPTi5zdHJpbmdpZnkodmFsKX1gXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB2YWxpZGF0ZTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydE5vZGVUeXBlKC4uLnR5cGVzOiBBcnJheTxzdHJpbmc+KTogRnVuY3Rpb24ge1xuICBmdW5jdGlvbiB2YWxpZGF0ZShub2RlLCBrZXksIHZhbCkge1xuICAgIGNvbnN0IHZhbGlkID0gdHlwZXMuZXZlcnkodHlwZSA9PiB0LmlzKHR5cGUsIHZhbCkpO1xuXG4gICAgaWYgKCF2YWxpZCkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgYFByb3BlcnR5ICR7a2V5fSBvZiAke25vZGUudHlwZX0gZXhwZWN0ZWQgbm9kZSB0byBiZSBvZiBhIHR5cGUgJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICB0eXBlc1xuICAgICAgICApfSBgICsgYGJ1dCBpbnN0ZWFkIGdvdCAke0pTT04uc3RyaW5naWZ5KHZhbCAmJiB2YWwudHlwZSl9YFxuICAgICAgKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdmFsaWRhdGU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnROb2RlT3JWYWx1ZVR5cGUoLi4udHlwZXM6IEFycmF5PHN0cmluZz4pOiBGdW5jdGlvbiB7XG4gIGZ1bmN0aW9uIHZhbGlkYXRlKG5vZGUsIGtleSwgdmFsKSB7XG4gICAgY29uc3QgdmFsaWQgPSB0eXBlcy5ldmVyeSh0eXBlID0+IGdldFR5cGUodmFsKSA9PT0gdHlwZSB8fCB0LmlzKHR5cGUsIHZhbCkpO1xuXG4gICAgaWYgKCF2YWxpZCkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgYFByb3BlcnR5ICR7a2V5fSBvZiAke25vZGUudHlwZX0gZXhwZWN0ZWQgbm9kZSB0byBiZSBvZiBhIHR5cGUgJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICB0eXBlc1xuICAgICAgICApfSBgICsgYGJ1dCBpbnN0ZWFkIGdvdCAke0pTT04uc3RyaW5naWZ5KHZhbCAmJiB2YWwudHlwZSl9YFxuICAgICAgKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdmFsaWRhdGU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnRWYWx1ZVR5cGUodHlwZTogc3RyaW5nKTogRnVuY3Rpb24ge1xuICBmdW5jdGlvbiB2YWxpZGF0ZShub2RlLCBrZXksIHZhbCkge1xuICAgIGNvbnN0IHZhbGlkID0gZ2V0VHlwZSh2YWwpID09PSB0eXBlO1xuXG4gICAgaWYgKCF2YWxpZCkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgYFByb3BlcnR5ICR7a2V5fSBleHBlY3RlZCB0eXBlIG9mICR7dHlwZX0gYnV0IGdvdCAke2dldFR5cGUodmFsKX1gXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB2YWxpZGF0ZTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydEFycmF5T2YoY2I6IEZ1bmN0aW9uKTogRnVuY3Rpb24ge1xuICByZXR1cm4gY2hhaW4oYXNzZXJ0VmFsdWVUeXBlKFwiYXJyYXlcIiksIGFzc2VydEVhY2goY2IpKTtcbn1cbiJdfQ==
226 |
--------------------------------------------------------------------------------
/src/graphql-ast-types-browser/definitions/init.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _slicedToArray = (function () {
4 | function sliceIterator(arr, i) {
5 | var _arr = [];
6 | var _n = true;
7 | var _d = false;
8 | var _e = undefined;
9 | try {
10 | for (
11 | var _i = arr[Symbol.iterator](), _s;
12 | !(_n = (_s = _i.next()).done);
13 | _n = true
14 | ) {
15 | _arr.push(_s.value);
16 | if (i && _arr.length === i) break;
17 | }
18 | } catch (err) {
19 | _d = true;
20 | _e = err;
21 | } finally {
22 | try {
23 | if (!_n && _i['return']) _i['return']();
24 | } finally {
25 | if (_d) throw _e;
26 | }
27 | }
28 | return _arr;
29 | }
30 | return function (arr, i) {
31 | if (Array.isArray(arr)) {
32 | return arr;
33 | } else if (Symbol.iterator in Object(arr)) {
34 | return sliceIterator(arr, i);
35 | } else {
36 | throw new TypeError(
37 | 'Invalid attempt to destructure non-iterable instance'
38 | );
39 | }
40 | };
41 | })();
42 |
43 | var _index = require('./index');
44 |
45 | var _index2 = _interopRequireDefault(_index);
46 |
47 | var _graphql = require('./graphql');
48 |
49 | var _graphql2 = _interopRequireDefault(_graphql);
50 |
51 | function _interopRequireDefault(obj) {
52 | return obj && obj.__esModule ? obj : { default: obj };
53 | }
54 |
55 | (0, _graphql2.default)().forEach(function (_ref) {
56 | var _ref2 = _slicedToArray(_ref, 2),
57 | name = _ref2[0],
58 | params = _ref2[1];
59 |
60 | return (0, _index2.default)(name, params);
61 | });
62 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kZWZpbml0aW9ucy9pbml0LmpzIl0sIm5hbWVzIjpbImZvckVhY2giLCJuYW1lIiwicGFyYW1zIl0sIm1hcHBpbmdzIjoiOzs7O0FBQUE7Ozs7QUFDQTs7Ozs7O0FBRUEseUJBQWFBLE9BQWIsQ0FBcUI7QUFBQTtBQUFBLE1BQUVDLElBQUY7QUFBQSxNQUFRQyxNQUFSOztBQUFBLFNBQW9CLHFCQUFXRCxJQUFYLEVBQWlCQyxNQUFqQixDQUFwQjtBQUFBLENBQXJCIiwiZmlsZSI6ImluaXQuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZGVmaW5lVHlwZSBmcm9tIFwiLi9pbmRleFwiO1xuaW1wb3J0IGdyYXBocWxEZWYgZnJvbSBcIi4vZ3JhcGhxbFwiO1xuXG5ncmFwaHFsRGVmKCkuZm9yRWFjaCgoW25hbWUsIHBhcmFtc10pID0+IGRlZmluZVR5cGUobmFtZSwgcGFyYW1zKSk7XG4iXX0=
63 |
--------------------------------------------------------------------------------
/src/graphql-ast-types-browser/index.d.ts:
--------------------------------------------------------------------------------
1 | // From the npm library 'graphql-ast-types-browser'
2 | import type {
3 | NameNode,
4 | DocumentNode,
5 | OperationDefinitionNode,
6 | VariableDefinitionNode,
7 | VariableNode,
8 | SelectionSetNode,
9 | FieldNode,
10 | ArgumentNode,
11 | FragmentSpreadNode,
12 | InlineFragmentNode,
13 | FragmentDefinitionNode,
14 | IntValueNode,
15 | FloatValueNode,
16 | StringValueNode,
17 | BooleanValueNode,
18 | NullValueNode,
19 | EnumValueNode,
20 | ListValueNode,
21 | ObjectValueNode,
22 | ObjectFieldNode,
23 | DirectiveNode,
24 | NamedTypeNode,
25 | ListTypeNode,
26 | NonNullTypeNode,
27 | SchemaDefinitionNode,
28 | OperationTypeDefinitionNode,
29 | ScalarTypeDefinitionNode,
30 | ObjectTypeDefinitionNode,
31 | FieldDefinitionNode,
32 | InputValueDefinitionNode,
33 | InterfaceTypeDefinitionNode,
34 | UnionTypeDefinitionNode,
35 | EnumTypeDefinitionNode,
36 | EnumValueDefinitionNode,
37 | InputObjectTypeDefinitionNode,
38 | TypeExtensionDefinitionNode,
39 | DirectiveDefinitionNode,
40 | OperationTypeNode,
41 | ASTNode,
42 | DefinitionNode,
43 | ValueNode,
44 | SelectionNode,
45 | TypeNode,
46 | TypeSystemDefinitionNode,
47 | TypeDefinitionNode,
48 | } from 'graphql/language/ast';
49 | declare function name(value: string): NameNode;
50 | declare function isName(node: any): boolean;
51 | declare function assertName(node: any): boolean;
52 | declare function document(definitions: Array): DocumentNode;
53 | declare function isDocument(node: any): boolean;
54 | declare function assertDocument(node: any): boolean;
55 | declare function operationDefinition(
56 | operation: OperationTypeNode,
57 | selectionSet: SelectionSetNode,
58 | name?: NameNode,
59 | variableDefinitions?: Array,
60 | directives?: Array
61 | ): OperationDefinitionNode;
62 | declare function isOperationDefinition(node: any): boolean;
63 | declare function assertOperationDefinition(node: any): boolean;
64 | declare function variableDefinition(
65 | variable: VariableNode,
66 | type: TypeNode,
67 | defaultValue?: ValueNode
68 | ): VariableDefinitionNode;
69 | declare function isVariableDefinition(node: any): boolean;
70 | declare function assertVariableDefinition(node: any): boolean;
71 | declare function variable(name: NameNode): VariableNode;
72 | declare function isVariable(node: any): boolean;
73 | declare function assertVariable(node: any): boolean;
74 | declare function selectionSet(
75 | selections: Array
76 | ): SelectionSetNode;
77 | declare function isSelectionSet(node: any): boolean;
78 | declare function assertSelectionSet(node: any): boolean;
79 | declare function field(
80 | name: NameNode,
81 | alias?: NameNode | null,
82 | arguments?: Array | null,
83 | directives?: Array | null,
84 | selectionSet?: SelectionSetNode | null
85 | ): FieldNode;
86 | declare function isField(node: any): boolean;
87 | declare function assertField(node: any): boolean;
88 | declare function argument(name: NameNode, value: ValueNode): ArgumentNode;
89 | declare function isArgument(node: any): boolean;
90 | declare function assertArgument(node: any): boolean;
91 | declare function fragmentSpread(
92 | name: NameNode,
93 | directives?: Array
94 | ): FragmentSpreadNode;
95 | declare function isFragmentSpread(node: any): boolean;
96 | declare function assertFragmentSpread(node: any): boolean;
97 | declare function inlineFragment(
98 | selectionSet: SelectionSetNode,
99 | typeCondition?: NamedTypeNode,
100 | directives?: Array
101 | ): InlineFragmentNode;
102 | declare function isInlineFragment(node: any): boolean;
103 | declare function assertInlineFragment(node: any): boolean;
104 | declare function fragmentDefinition(
105 | name: NameNode,
106 | typeCondition: NamedTypeNode,
107 | selectionSet: SelectionSetNode,
108 | directives?: Array
109 | ): FragmentDefinitionNode;
110 | declare function isFragmentDefinition(node: any): boolean;
111 | declare function assertFragmentDefinition(node: any): boolean;
112 | declare function intValue(value: string): IntValueNode;
113 | declare function isIntValue(node: any): boolean;
114 | declare function assertIntValue(node: any): boolean;
115 | declare function floatValue(value: string): FloatValueNode;
116 | declare function isFloatValue(node: any): boolean;
117 | declare function assertFloatValue(node: any): boolean;
118 | declare function stringValue(value: string): StringValueNode;
119 | declare function isStringValue(node: any): boolean;
120 | declare function assertStringValue(node: any): boolean;
121 | declare function booleanValue(value: boolean): BooleanValueNode;
122 | declare function isBooleanValue(node: any): boolean;
123 | declare function assertBooleanValue(node: any): boolean;
124 | declare function nullValue(): NullValueNode;
125 | declare function isNullValue(node: any): boolean;
126 | declare function assertNullValue(node: any): boolean;
127 | declare function enumValue(value: string): EnumValueNode;
128 | declare function isEnumValue(node: any): boolean;
129 | declare function assertEnumValue(node: any): boolean;
130 | declare function listValue(values: Array): ListValueNode;
131 | declare function isListValue(node: any): boolean;
132 | declare function assertListValue(node: any): boolean;
133 | declare function objectValue(fields: Array): ObjectValueNode;
134 | declare function isObjectValue(node: any): boolean;
135 | declare function assertObjectValue(node: any): boolean;
136 | declare function objectField(name: NameNode, value: ValueNode): ObjectFieldNode;
137 | declare function isObjectField(node: any): boolean;
138 | declare function assertObjectField(node: any): boolean;
139 | declare function directive(
140 | name: NameNode,
141 | arguments?: Array
142 | ): DirectiveNode;
143 | declare function isDirective(node: any): boolean;
144 | declare function assertDirective(node: any): boolean;
145 | declare function namedType(name: NameNode): NamedTypeNode;
146 | declare function isNamedType(node: any): boolean;
147 | declare function assertNamedType(node: any): boolean;
148 | declare function listType(type: TypeNode): ListTypeNode;
149 | declare function isListType(node: any): boolean;
150 | declare function assertListType(node: any): boolean;
151 | declare function nonNullType(
152 | type: NamedTypeNode | ListTypeNode
153 | ): NonNullTypeNode;
154 | declare function isNonNullType(node: any): boolean;
155 | declare function assertNonNullType(node: any): boolean;
156 | declare function schemaDefinition(
157 | directives: Array,
158 | operationTypes: Array
159 | ): SchemaDefinitionNode;
160 | declare function isSchemaDefinition(node: any): boolean;
161 | declare function assertSchemaDefinition(node: any): boolean;
162 | declare function operationTypeDefinition(
163 | operation: OperationTypeNode,
164 | type: NamedTypeNode
165 | ): OperationTypeDefinitionNode;
166 | declare function isOperationTypeDefinition(node: any): boolean;
167 | declare function assertOperationTypeDefinition(node: any): boolean;
168 | declare function scalarTypeDefinition(
169 | name: NameNode,
170 | directives?: Array
171 | ): ScalarTypeDefinitionNode;
172 | declare function isScalarTypeDefinition(node: any): boolean;
173 | declare function assertScalarTypeDefinition(node: any): boolean;
174 | declare function objectTypeDefinition(
175 | name: NameNode,
176 | fields: Array,
177 | interfaces?: Array,
178 | directives?: Array
179 | ): ObjectTypeDefinitionNode;
180 | declare function isObjectTypeDefinition(node: any): boolean;
181 | declare function assertObjectTypeDefinition(node: any): boolean;
182 | declare function fieldDefinition(
183 | name: NameNode,
184 | arguments: Array,
185 | type: TypeNode,
186 | directives?: Array
187 | ): FieldDefinitionNode;
188 | declare function isFieldDefinition(node: any): boolean;
189 | declare function assertFieldDefinition(node: any): boolean;
190 | declare function inputValueDefinition(
191 | name: NameNode,
192 | type: TypeNode,
193 | defaultValue?: ValueNode,
194 | directives?: Array
195 | ): InputValueDefinitionNode;
196 | declare function isInputValueDefinition(node: any): boolean;
197 | declare function assertInputValueDefinition(node: any): boolean;
198 | declare function interfaceTypeDefinition(
199 | name: NameNode,
200 | fields: Array,
201 | directives?: Array
202 | ): InterfaceTypeDefinitionNode;
203 | declare function isInterfaceTypeDefinition(node: any): boolean;
204 | declare function assertInterfaceTypeDefinition(node: any): boolean;
205 | declare function unionTypeDefinition(
206 | name: NameNode,
207 | types: Array,
208 | directives?: Array
209 | ): UnionTypeDefinitionNode;
210 | declare function isUnionTypeDefinition(node: any): boolean;
211 | declare function assertUnionTypeDefinition(node: any): boolean;
212 | declare function enumTypeDefinition(
213 | name: NameNode,
214 | values: Array,
215 | directives?: Array
216 | ): EnumTypeDefinitionNode;
217 | declare function isEnumTypeDefinition(node: any): boolean;
218 | declare function assertEnumTypeDefinition(node: any): boolean;
219 | declare function enumValueDefinition(
220 | name: NameNode,
221 | directives?: Array
222 | ): EnumValueDefinitionNode;
223 | declare function isEnumValueDefinition(node: any): boolean;
224 | declare function assertEnumValueDefinition(node: any): boolean;
225 | declare function inputObjectTypeDefinition(
226 | name: NameNode,
227 | fields: Array,
228 | directives?: Array
229 | ): InputObjectTypeDefinitionNode;
230 | declare function isInputObjectTypeDefinition(node: any): boolean;
231 | declare function assertInputObjectTypeDefinition(node: any): boolean;
232 | declare function typeExtensionDefinition(
233 | definition: ObjectTypeDefinitionNode
234 | ): TypeExtensionDefinitionNode;
235 | declare function isTypeExtensionDefinition(node: any): boolean;
236 | declare function assertTypeExtensionDefinition(node: any): boolean;
237 | declare function directiveDefinition(
238 | name: NameNode,
239 | locations: Array,
240 | arguments?: Array
241 | ): DirectiveDefinitionNode;
242 | declare function isDirectiveDefinition(node: any): boolean;
243 | declare function assertDirectiveDefinition(node: any): boolean;
244 |
245 | declare function is(nodeName: string, node: any): boolean;
246 |
--------------------------------------------------------------------------------
/src/graphql-ast-types-browser/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true,
5 | });
6 |
7 | var _typeof =
8 | typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
9 | ? function (obj) {
10 | return typeof obj;
11 | }
12 | : function (obj) {
13 | return obj &&
14 | typeof Symbol === 'function' &&
15 | obj.constructor === Symbol &&
16 | obj !== Symbol.prototype
17 | ? 'symbol'
18 | : typeof obj;
19 | };
20 |
21 | exports.is = is;
22 | exports.isType = isType;
23 | exports.validate = validate;
24 | exports.shallowEqual = shallowEqual;
25 |
26 | var _require = require('./definitions/init');
27 |
28 | var _require = require('./definitions'),
29 | ALIAS_KEYS = _require.ALIAS_KEYS,
30 | NODE_FIELDS = _require.NODE_FIELDS,
31 | BUILDER_KEYS = _require.BUILDER_KEYS;
32 |
33 | var t = exports; // Maps all exports to t
34 |
35 | /**
36 | * Registers `is[Type]` and `assert[Type]` generated functions for a given `type`.
37 | * Pass `skipAliasCheck` to force it to directly compare `node.type` with `type`.
38 | */
39 |
40 | function registerType(type) {
41 | var key = 'is' + type;
42 |
43 | var _isType =
44 | t[key] !== undefined
45 | ? t[key]
46 | : (t[key] = function (node, opts) {
47 | return t.is(type, node, opts);
48 | });
49 |
50 | t['assert' + type] = function (node) {
51 | var opts =
52 | arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
53 |
54 | if (!_isType(node, opts)) {
55 | throw new Error(
56 | 'Expected type "' + type + '" with option ' + JSON.stringify(opts)
57 | );
58 | }
59 | };
60 | }
61 |
62 | exports.ALIAS_KEYS = ALIAS_KEYS;
63 | exports.NODE_FIELDS = NODE_FIELDS;
64 | exports.BUILDER_KEYS = BUILDER_KEYS;
65 |
66 | /**
67 | * Registers `is[Type]` and `assert[Type]` for all types.
68 | */
69 |
70 | for (var type in t.NODE_FIELDS) {
71 | registerType(type);
72 | }
73 |
74 | /**
75 | * Flip `ALIAS_KEYS` for faster access in the reverse direction.
76 | */
77 |
78 | var TYPES = (exports.TYPES = []);
79 |
80 | t.FLIPPED_ALIAS_KEYS = Object.keys(t.ALIAS_KEYS).reduce(function (acc, type) {
81 | var aliasKeys = t.ALIAS_KEYS[type];
82 |
83 | aliasKeys.forEach(function (alias) {
84 | if (acc[alias] === undefined) {
85 | TYPES.push(alias); // Populate `TYPES` with FLIPPED_ALIAS_KEY(S)
86 |
87 | // Registers `is[Alias]` and `assert[Alias]` functions for all aliases.
88 | t[alias.toUpperCase() + '_TYPES'] = acc[alias];
89 | registerType(alias);
90 |
91 | acc[alias] = [];
92 | }
93 |
94 | acc[alias].push(type);
95 | });
96 |
97 | return acc;
98 | }, {});
99 |
100 | /**
101 | * Returns whether `node` is of given `type`.
102 | *
103 | * For better performance, use this instead of `is[Type]` when `type` is unknown.
104 | * Optionally, pass `skipAliasCheck` to directly compare `node.type` with `type`.
105 | */
106 |
107 | function is(type, node, opts) {
108 | if (
109 | node === null ||
110 | (typeof node === 'undefined' ? 'undefined' : _typeof(node)) !== 'object'
111 | ) {
112 | return false;
113 | }
114 |
115 | var matches = isType(node.kind, type);
116 | if (!matches) {
117 | return false;
118 | }
119 |
120 | if (typeof opts === 'undefined') {
121 | return true;
122 | } else {
123 | return t.shallowEqual(node, opts);
124 | }
125 | }
126 |
127 | /**
128 | * Test if a `nodeType` is a `targetType` or if `targetType` is an alias of `nodeType`.
129 | */
130 |
131 | function isType(nodeType, targetType) {
132 | if (nodeType === targetType) {
133 | return true;
134 | }
135 |
136 | // This is a fast-path. If the test above failed, but an alias key is found, then the
137 | // targetType was a primary node type, so there's no need to check the aliases.
138 | if (t.ALIAS_KEYS[targetType]) {
139 | return false;
140 | }
141 |
142 | var aliases = t.FLIPPED_ALIAS_KEYS[targetType];
143 | if (aliases) {
144 | if (aliases[0] === nodeType) {
145 | return true;
146 | }
147 |
148 | var _iteratorNormalCompletion = true;
149 | var _didIteratorError = false;
150 | var _iteratorError = undefined;
151 |
152 | try {
153 | for (
154 | var _iterator = aliases[Symbol.iterator](), _step;
155 | !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
156 | _iteratorNormalCompletion = true
157 | ) {
158 | var alias = _step.value;
159 |
160 | if (nodeType === alias) {
161 | return true;
162 | }
163 | }
164 | } catch (err) {
165 | _didIteratorError = true;
166 | _iteratorError = err;
167 | } finally {
168 | try {
169 | if (!_iteratorNormalCompletion && _iterator.return) {
170 | _iterator.return();
171 | }
172 | } finally {
173 | if (_didIteratorError) {
174 | throw _iteratorError;
175 | }
176 | }
177 | }
178 | }
179 |
180 | return false;
181 | }
182 |
183 | /**
184 | * For each call of #defineType, the following expression evalutates and generates
185 | * a builder function that validates incoming arguments and returns a valid AST node.
186 | */
187 |
188 | var _loop = function _loop(_type) {
189 | var keys = t.BUILDER_KEYS[_type];
190 | var fields = t.NODE_FIELDS[_type];
191 |
192 | function builder() {
193 | for (
194 | var _len = arguments.length, args = Array(_len), _key = 0;
195 | _key < _len;
196 | _key++
197 | ) {
198 | args[_key] = arguments[_key];
199 | }
200 |
201 | if (args.length > keys.length) {
202 | throw new Error(
203 | 't.' +
204 | _type +
205 | ': Too many arguments passed. Received ' +
206 | args.length +
207 | ' but can receive ' +
208 | ('no more than ' + keys.length)
209 | );
210 | }
211 |
212 | var node = keys.reduce(
213 | function (node, key, i) {
214 | node[key] = args[i] === undefined ? fields[key].default : args[i];
215 | return node;
216 | },
217 | { kind: _type }
218 | );
219 |
220 | for (var key in node) {
221 | validate(node, key, node[key]);
222 | }
223 |
224 | return node;
225 | }
226 |
227 | t[_type[0].toLowerCase() + _type.slice(1)] = builder;
228 | };
229 |
230 | for (var _type in t.BUILDER_KEYS) {
231 | _loop(_type);
232 | }
233 |
234 | /**
235 | * Executes the field validators for a given node
236 | */
237 |
238 | function validate(node, key, val) {
239 | if (
240 | node === null ||
241 | (typeof node === 'undefined' ? 'undefined' : _typeof(node)) !== 'object'
242 | ) {
243 | return;
244 | }
245 |
246 | var fields = t.NODE_FIELDS[node.kind];
247 | if (fields === undefined) {
248 | return;
249 | }
250 |
251 | var field = fields[key];
252 | if (field === undefined || field.validate === undefined) {
253 | return;
254 | }
255 |
256 | if (field.optional && (val === undefined || val === null)) {
257 | return;
258 | }
259 |
260 | field.validate(node, key, val);
261 | }
262 |
263 | /**
264 | * Test if an object is shallowly equal.
265 | */
266 |
267 | function shallowEqual(actual, expected) {
268 | for (var key in expected) {
269 | if (expected.hasOwnProperty(key) && actual[key] !== expected[key]) {
270 | return false;
271 | }
272 | }
273 |
274 | return true;
275 | }
276 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC5qcyJdLCJuYW1lcyI6WyJpcyIsImlzVHlwZSIsInZhbGlkYXRlIiwic2hhbGxvd0VxdWFsIiwicmVxdWlyZSIsIkFMSUFTX0tFWVMiLCJOT0RFX0ZJRUxEUyIsIkJVSUxERVJfS0VZUyIsInQiLCJleHBvcnRzIiwicmVnaXN0ZXJUeXBlIiwidHlwZSIsImtleSIsIl9pc1R5cGUiLCJ1bmRlZmluZWQiLCJub2RlIiwib3B0cyIsIkVycm9yIiwiSlNPTiIsInN0cmluZ2lmeSIsIlRZUEVTIiwiRkxJUFBFRF9BTElBU19LRVlTIiwiT2JqZWN0Iiwia2V5cyIsInJlZHVjZSIsImFjYyIsImFsaWFzS2V5cyIsImZvckVhY2giLCJhbGlhcyIsInB1c2giLCJ0b1VwcGVyQ2FzZSIsIm1hdGNoZXMiLCJraW5kIiwibm9kZVR5cGUiLCJ0YXJnZXRUeXBlIiwiYWxpYXNlcyIsImZpZWxkcyIsImJ1aWxkZXIiLCJhcmdzIiwibGVuZ3RoIiwiaSIsImRlZmF1bHQiLCJ0b0xvd2VyQ2FzZSIsInNsaWNlIiwidmFsIiwiZmllbGQiLCJvcHRpb25hbCIsImFjdHVhbCIsImV4cGVjdGVkIiwiaGFzT3duUHJvcGVydHkiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O1FBc0VnQkEsRSxHQUFBQSxFO1FBcUJBQyxNLEdBQUFBLE07UUFrRUFDLFEsR0FBQUEsUTtRQTBCQUMsWSxHQUFBQSxZO0FBckxoQkMsUUFBUSxvQkFBUjs7ZUFFa0RBLFFBQVEsZUFBUixDO0lBQTFDQyxVLFlBQUFBLFU7SUFBWUMsVyxZQUFBQSxXO0lBQWFDLFksWUFBQUEsWTs7QUFFakMsSUFBTUMsSUFBSUMsT0FBVixDLENBQW1COztBQUVuQjs7Ozs7QUFLQSxTQUFTQyxZQUFULENBQXNCQyxJQUF0QixFQUFvQztBQUNsQyxNQUFNQyxhQUFXRCxJQUFqQjs7QUFFQSxNQUFNRSxVQUFVTCxFQUFFSSxHQUFGLE1BQVdFLFNBQVgsR0FDWk4sRUFBRUksR0FBRixDQURZLEdBRVpKLEVBQUVJLEdBQUYsSUFBUyxVQUFDRyxJQUFELEVBQU9DLElBQVA7QUFBQSxXQUFnQlIsRUFBRVIsRUFBRixDQUFLVyxJQUFMLEVBQVdJLElBQVgsRUFBaUJDLElBQWpCLENBQWhCO0FBQUEsR0FGYjs7QUFJQVIsZUFBV0csSUFBWCxJQUFxQixVQUFDSSxJQUFELEVBQXFCO0FBQUEsUUFBZEMsSUFBYyx1RUFBUCxFQUFPOztBQUN4QyxRQUFJLENBQUNILFFBQVFFLElBQVIsRUFBY0MsSUFBZCxDQUFMLEVBQTBCO0FBQ3hCLFlBQU0sSUFBSUMsS0FBSixxQkFBNEJOLElBQTVCLHNCQUFpRE8sS0FBS0MsU0FBTCxDQUFlSCxJQUFmLENBQWpELENBQU47QUFDRDtBQUNGLEdBSkQ7QUFLRDs7UUFFUVgsVSxHQUFBQSxVO1FBQVlDLFcsR0FBQUEsVztRQUFhQyxZLEdBQUFBLFk7O0FBRWxDOzs7O0FBSUEsS0FBSyxJQUFNSSxJQUFYLElBQW1CSCxFQUFFRixXQUFyQixFQUFrQztBQUNoQ0ksZUFBYUMsSUFBYjtBQUNEOztBQUVEOzs7O0FBSU8sSUFBTVMsd0JBQVEsRUFBZDs7QUFFUFosRUFBRWEsa0JBQUYsR0FBdUJDLE9BQU9DLElBQVAsQ0FBWWYsRUFBRUgsVUFBZCxFQUEwQm1CLE1BQTFCLENBQWlDLFVBQUNDLEdBQUQsRUFBTWQsSUFBTixFQUFlO0FBQ3JFLE1BQU1lLFlBQVlsQixFQUFFSCxVQUFGLENBQWFNLElBQWIsQ0FBbEI7O0FBRUFlLFlBQVVDLE9BQVYsQ0FBa0IsaUJBQVM7QUFDekIsUUFBSUYsSUFBSUcsS0FBSixNQUFlZCxTQUFuQixFQUE4QjtBQUM1Qk0sWUFBTVMsSUFBTixDQUFXRCxLQUFYLEVBRDRCLENBQ1Q7O0FBRW5CO0FBQ0FwQixRQUFLb0IsTUFBTUUsV0FBTixFQUFMLGVBQW9DTCxJQUFJRyxLQUFKLENBQXBDO0FBQ0FsQixtQkFBYWtCLEtBQWI7O0FBRUFILFVBQUlHLEtBQUosSUFBYSxFQUFiO0FBQ0Q7O0FBRURILFFBQUlHLEtBQUosRUFBV0MsSUFBWCxDQUFnQmxCLElBQWhCO0FBQ0QsR0FaRDs7QUFjQSxTQUFPYyxHQUFQO0FBQ0QsQ0FsQnNCLEVBa0JwQixFQWxCb0IsQ0FBdkI7O0FBb0JBOzs7Ozs7O0FBT08sU0FBU3pCLEVBQVQsQ0FBWVcsSUFBWixFQUEwQkksSUFBMUIsRUFBd0NDLElBQXhDLEVBQWdFO0FBQ3JFLE1BQUlELFNBQVMsSUFBVCxJQUFpQixRQUFPQSxJQUFQLHlDQUFPQSxJQUFQLE9BQWdCLFFBQXJDLEVBQStDO0FBQzdDLFdBQU8sS0FBUDtBQUNEOztBQUVELE1BQU1nQixVQUFVOUIsT0FBT2MsS0FBS2lCLElBQVosRUFBa0JyQixJQUFsQixDQUFoQjtBQUNBLE1BQUksQ0FBQ29CLE9BQUwsRUFBYztBQUNaLFdBQU8sS0FBUDtBQUNEOztBQUVELE1BQUksT0FBT2YsSUFBUCxLQUFnQixXQUFwQixFQUFpQztBQUMvQixXQUFPLElBQVA7QUFDRCxHQUZELE1BRU87QUFDTCxXQUFPUixFQUFFTCxZQUFGLENBQWVZLElBQWYsRUFBcUJDLElBQXJCLENBQVA7QUFDRDtBQUNGOztBQUVEOzs7O0FBSU8sU0FBU2YsTUFBVCxDQUFnQmdDLFFBQWhCLEVBQWtDQyxVQUFsQyxFQUErRDtBQUNwRSxNQUFJRCxhQUFhQyxVQUFqQixFQUE2QjtBQUMzQixXQUFPLElBQVA7QUFDRDs7QUFFRDtBQUNBO0FBQ0EsTUFBSTFCLEVBQUVILFVBQUYsQ0FBYTZCLFVBQWIsQ0FBSixFQUE4QjtBQUM1QixXQUFPLEtBQVA7QUFDRDs7QUFFRCxNQUFNQyxVQUEwQjNCLEVBQUVhLGtCQUFGLENBQXFCYSxVQUFyQixDQUFoQztBQUNBLE1BQUlDLE9BQUosRUFBYTtBQUNYLFFBQUlBLFFBQVEsQ0FBUixNQUFlRixRQUFuQixFQUE2QjtBQUMzQixhQUFPLElBQVA7QUFDRDs7QUFIVTtBQUFBO0FBQUE7O0FBQUE7QUFLWCwyQkFBb0JFLE9BQXBCLDhIQUE2QjtBQUFBLFlBQWxCUCxLQUFrQjs7QUFDM0IsWUFBSUssYUFBYUwsS0FBakIsRUFBd0I7QUFDdEIsaUJBQU8sSUFBUDtBQUNEO0FBQ0Y7QUFUVTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBVVo7O0FBRUQsU0FBTyxLQUFQO0FBQ0Q7O0FBRUQ7Ozs7OzJCQUtXakIsSztBQUNULE1BQU1ZLE9BQU9mLEVBQUVELFlBQUYsQ0FBZUksS0FBZixDQUFiO0FBQ0EsTUFBTXlCLFNBQVM1QixFQUFFRixXQUFGLENBQWNLLEtBQWQsQ0FBZjs7QUFFQSxXQUFTMEIsT0FBVCxHQUEwQjtBQUFBLHNDQUFOQyxJQUFNO0FBQU5BLFVBQU07QUFBQTs7QUFDeEIsUUFBSUEsS0FBS0MsTUFBTCxHQUFjaEIsS0FBS2dCLE1BQXZCLEVBQStCO0FBQzdCLFlBQU0sSUFBSXRCLEtBQUosQ0FDSixPQUFLTixLQUFMLDhDQUFrRDJCLEtBQUtDLE1BQXZELDRDQUNrQmhCLEtBQUtnQixNQUR2QixDQURJLENBQU47QUFJRDs7QUFFRCxRQUFNeEIsT0FBT1EsS0FBS0MsTUFBTCxDQUNYLFVBQUNULElBQUQsRUFBT0gsR0FBUCxFQUFZNEIsQ0FBWixFQUFrQjtBQUNoQnpCLFdBQUtILEdBQUwsSUFBYTBCLEtBQUtFLENBQUwsTUFBWTFCLFNBQVosR0FBd0JzQixPQUFPeEIsR0FBUCxFQUFZNkIsT0FBcEMsR0FBOENILEtBQUtFLENBQUwsQ0FBM0Q7QUFDQSxhQUFPekIsSUFBUDtBQUNELEtBSlUsRUFLWCxFQUFFaUIsTUFBTXJCLEtBQVIsRUFMVyxDQUFiOztBQVFBLFNBQUssSUFBTUMsR0FBWCxJQUFrQkcsSUFBbEIsRUFBd0I7QUFDdEJiLGVBQVNhLElBQVQsRUFBZUgsR0FBZixFQUFvQkcsS0FBS0gsR0FBTCxDQUFwQjtBQUNEOztBQUVELFdBQU9HLElBQVA7QUFDRDs7QUFFRFAsSUFBRUcsTUFBSyxDQUFMLEVBQVErQixXQUFSLEtBQXdCL0IsTUFBS2dDLEtBQUwsQ0FBVyxDQUFYLENBQTFCLElBQTJDTixPQUEzQzs7O0FBM0JGLEtBQUssSUFBTTFCLEtBQVgsSUFBbUJILEVBQUVELFlBQXJCLEVBQW1DO0FBQUEsUUFBeEJJLEtBQXdCO0FBNEJsQzs7QUFFRDs7OztBQUlPLFNBQVNULFFBQVQsQ0FBa0JhLElBQWxCLEVBQWlDSCxHQUFqQyxFQUE4Q2dDLEdBQTlDLEVBQXdEO0FBQzdELE1BQUk3QixTQUFTLElBQVQsSUFBaUIsUUFBT0EsSUFBUCx5Q0FBT0EsSUFBUCxPQUFnQixRQUFyQyxFQUErQztBQUM3QztBQUNEOztBQUVELE1BQU1xQixTQUFTNUIsRUFBRUYsV0FBRixDQUFjUyxLQUFLaUIsSUFBbkIsQ0FBZjtBQUNBLE1BQUlJLFdBQVd0QixTQUFmLEVBQTBCO0FBQ3hCO0FBQ0Q7O0FBRUQsTUFBTStCLFFBQVFULE9BQU94QixHQUFQLENBQWQ7QUFDQSxNQUFJaUMsVUFBVS9CLFNBQVYsSUFBdUIrQixNQUFNM0MsUUFBTixLQUFtQlksU0FBOUMsRUFBeUQ7QUFDdkQ7QUFDRDs7QUFFRCxNQUFJK0IsTUFBTUMsUUFBTixLQUFtQkYsUUFBUTlCLFNBQVIsSUFBcUI4QixRQUFRLElBQWhELENBQUosRUFBMkQ7QUFDekQ7QUFDRDs7QUFFREMsUUFBTTNDLFFBQU4sQ0FBZWEsSUFBZixFQUFxQkgsR0FBckIsRUFBMEJnQyxHQUExQjtBQUNEOztBQUVEOzs7O0FBSU8sU0FBU3pDLFlBQVQsQ0FBc0I0QyxNQUF0QixFQUFzQ0MsUUFBdEMsRUFBaUU7QUFDdEUsT0FBSyxJQUFNcEMsR0FBWCxJQUFrQm9DLFFBQWxCLEVBQTRCO0FBQzFCLFFBQUlBLFNBQVNDLGNBQVQsQ0FBd0JyQyxHQUF4QixLQUFnQ21DLE9BQU9uQyxHQUFQLE1BQWdCb0MsU0FBU3BDLEdBQVQsQ0FBcEQsRUFBbUU7QUFDakUsYUFBTyxLQUFQO0FBQ0Q7QUFDRjs7QUFFRCxTQUFPLElBQVA7QUFDRCIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEBmbG93XG5cbnJlcXVpcmUoJy4vZGVmaW5pdGlvbnMvaW5pdCcpO1xuXG5jb25zdCB7IEFMSUFTX0tFWVMsIE5PREVfRklFTERTLCBCVUlMREVSX0tFWVMgfSA9IHJlcXVpcmUoJy4vZGVmaW5pdGlvbnMnKTtcblxuY29uc3QgdCA9IGV4cG9ydHM7IC8vIE1hcHMgYWxsIGV4cG9ydHMgdG8gdFxuXG4vKipcbiAqIFJlZ2lzdGVycyBgaXNbVHlwZV1gIGFuZCBgYXNzZXJ0W1R5cGVdYCBnZW5lcmF0ZWQgZnVuY3Rpb25zIGZvciBhIGdpdmVuIGB0eXBlYC5cbiAqIFBhc3MgYHNraXBBbGlhc0NoZWNrYCB0byBmb3JjZSBpdCB0byBkaXJlY3RseSBjb21wYXJlIGBub2RlLnR5cGVgIHdpdGggYHR5cGVgLlxuICovXG5cbmZ1bmN0aW9uIHJlZ2lzdGVyVHlwZSh0eXBlOiBzdHJpbmcpIHtcbiAgY29uc3Qga2V5ID0gYGlzJHt0eXBlfWA7XG5cbiAgY29uc3QgX2lzVHlwZSA9IHRba2V5XSAhPT0gdW5kZWZpbmVkXG4gICAgPyB0W2tleV1cbiAgICA6IHRba2V5XSA9IChub2RlLCBvcHRzKSA9PiB0LmlzKHR5cGUsIG5vZGUsIG9wdHMpO1xuXG4gIHRbYGFzc2VydCR7dHlwZX1gXSA9IChub2RlLCBvcHRzID0ge30pID0+IHtcbiAgICBpZiAoIV9pc1R5cGUobm9kZSwgb3B0cykpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgdHlwZSBcIiR7dHlwZX1cIiB3aXRoIG9wdGlvbiAke0pTT04uc3RyaW5naWZ5KG9wdHMpfWApO1xuICAgIH1cbiAgfTtcbn1cblxuZXhwb3J0IHsgQUxJQVNfS0VZUywgTk9ERV9GSUVMRFMsIEJVSUxERVJfS0VZUyB9O1xuXG4vKipcbiAqIFJlZ2lzdGVycyBgaXNbVHlwZV1gIGFuZCBgYXNzZXJ0W1R5cGVdYCBmb3IgYWxsIHR5cGVzLlxuICovXG5cbmZvciAoY29uc3QgdHlwZSBpbiB0Lk5PREVfRklFTERTKSB7XG4gIHJlZ2lzdGVyVHlwZSh0eXBlKTtcbn1cblxuLyoqXG4gKiBGbGlwIGBBTElBU19LRVlTYCBmb3IgZmFzdGVyIGFjY2VzcyBpbiB0aGUgcmV2ZXJzZSBkaXJlY3Rpb24uXG4gKi9cblxuZXhwb3J0IGNvbnN0IFRZUEVTID0gW107XG5cbnQuRkxJUFBFRF9BTElBU19LRVlTID0gT2JqZWN0LmtleXModC5BTElBU19LRVlTKS5yZWR1Y2UoKGFjYywgdHlwZSkgPT4ge1xuICBjb25zdCBhbGlhc0tleXMgPSB0LkFMSUFTX0tFWVNbdHlwZV07XG5cbiAgYWxpYXNLZXlzLmZvckVhY2goYWxpYXMgPT4ge1xuICAgIGlmIChhY2NbYWxpYXNdID09PSB1bmRlZmluZWQpIHtcbiAgICAgIFRZUEVTLnB1c2goYWxpYXMpOyAvLyBQb3B1bGF0ZSBgVFlQRVNgIHdpdGggRkxJUFBFRF9BTElBU19LRVkoUylcblxuICAgICAgLy8gUmVnaXN0ZXJzIGBpc1tBbGlhc11gIGFuZCBgYXNzZXJ0W0FsaWFzXWAgZnVuY3Rpb25zIGZvciBhbGwgYWxpYXNlcy5cbiAgICAgIHRbYCR7YWxpYXMudG9VcHBlckNhc2UoKX1fVFlQRVNgXSA9IGFjY1thbGlhc107XG4gICAgICByZWdpc3RlclR5cGUoYWxpYXMpO1xuXG4gICAgICBhY2NbYWxpYXNdID0gW107XG4gICAgfVxuXG4gICAgYWNjW2FsaWFzXS5wdXNoKHR5cGUpO1xuICB9KTtcblxuICByZXR1cm4gYWNjO1xufSwge30pO1xuXG4vKipcbiAqIFJldHVybnMgd2hldGhlciBgbm9kZWAgaXMgb2YgZ2l2ZW4gYHR5cGVgLlxuICpcbiAqIEZvciBiZXR0ZXIgcGVyZm9ybWFuY2UsIHVzZSB0aGlzIGluc3RlYWQgb2YgYGlzW1R5cGVdYCB3aGVuIGB0eXBlYCBpcyB1bmtub3duLlxuICogT3B0aW9uYWxseSwgcGFzcyBgc2tpcEFsaWFzQ2hlY2tgIHRvIGRpcmVjdGx5IGNvbXBhcmUgYG5vZGUudHlwZWAgd2l0aCBgdHlwZWAuXG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIGlzKHR5cGU6IHN0cmluZywgbm9kZTogT2JqZWN0LCBvcHRzPzogT2JqZWN0KTogYm9vbGVhbiB7XG4gIGlmIChub2RlID09PSBudWxsIHx8IHR5cGVvZiBub2RlICE9PSAnb2JqZWN0Jykge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIGNvbnN0IG1hdGNoZXMgPSBpc1R5cGUobm9kZS5raW5kLCB0eXBlKTtcbiAgaWYgKCFtYXRjaGVzKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgaWYgKHR5cGVvZiBvcHRzID09PSAndW5kZWZpbmVkJykge1xuICAgIHJldHVybiB0cnVlO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiB0LnNoYWxsb3dFcXVhbChub2RlLCBvcHRzKTtcbiAgfVxufVxuXG4vKipcbiAqIFRlc3QgaWYgYSBgbm9kZVR5cGVgIGlzIGEgYHRhcmdldFR5cGVgIG9yIGlmIGB0YXJnZXRUeXBlYCBpcyBhbiBhbGlhcyBvZiBgbm9kZVR5cGVgLlxuICovXG5cbmV4cG9ydCBmdW5jdGlvbiBpc1R5cGUobm9kZVR5cGU6IHN0cmluZywgdGFyZ2V0VHlwZTogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGlmIChub2RlVHlwZSA9PT0gdGFyZ2V0VHlwZSkge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgLy8gVGhpcyBpcyBhIGZhc3QtcGF0aC4gSWYgdGhlIHRlc3QgYWJvdmUgZmFpbGVkLCBidXQgYW4gYWxpYXMga2V5IGlzIGZvdW5kLCB0aGVuIHRoZVxuICAvLyB0YXJnZXRUeXBlIHdhcyBhIHByaW1hcnkgbm9kZSB0eXBlLCBzbyB0aGVyZSdzIG5vIG5lZWQgdG8gY2hlY2sgdGhlIGFsaWFzZXMuXG4gIGlmICh0LkFMSUFTX0tFWVNbdGFyZ2V0VHlwZV0pIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBjb25zdCBhbGlhc2VzOiA/QXJyYXk8c3RyaW5nPiA9IHQuRkxJUFBFRF9BTElBU19LRVlTW3RhcmdldFR5cGVdO1xuICBpZiAoYWxpYXNlcykge1xuICAgIGlmIChhbGlhc2VzWzBdID09PSBub2RlVHlwZSkge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgZm9yIChjb25zdCBhbGlhcyBvZiBhbGlhc2VzKSB7XG4gICAgICBpZiAobm9kZVR5cGUgPT09IGFsaWFzKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiBmYWxzZTtcbn1cblxuLyoqXG4gKiBGb3IgZWFjaCBjYWxsIG9mICNkZWZpbmVUeXBlLCB0aGUgZm9sbG93aW5nIGV4cHJlc3Npb24gZXZhbHV0YXRlcyBhbmQgZ2VuZXJhdGVzXG4gKiBhIGJ1aWxkZXIgZnVuY3Rpb24gdGhhdCB2YWxpZGF0ZXMgaW5jb21pbmcgYXJndW1lbnRzIGFuZCByZXR1cm5zIGEgdmFsaWQgQVNUIG5vZGUuXG4gKi9cblxuZm9yIChjb25zdCB0eXBlIGluIHQuQlVJTERFUl9LRVlTKSB7XG4gIGNvbnN0IGtleXMgPSB0LkJVSUxERVJfS0VZU1t0eXBlXTtcbiAgY29uc3QgZmllbGRzID0gdC5OT0RFX0ZJRUxEU1t0eXBlXTtcblxuICBmdW5jdGlvbiBidWlsZGVyKC4uLmFyZ3MpIHtcbiAgICBpZiAoYXJncy5sZW5ndGggPiBrZXlzLmxlbmd0aCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgdC4ke3R5cGV9OiBUb28gbWFueSBhcmd1bWVudHMgcGFzc2VkLiBSZWNlaXZlZCAke2FyZ3MubGVuZ3RofSBidXQgY2FuIHJlY2VpdmUgYCArXG4gICAgICAgICAgYG5vIG1vcmUgdGhhbiAke2tleXMubGVuZ3RofWBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgY29uc3Qgbm9kZSA9IGtleXMucmVkdWNlKFxuICAgICAgKG5vZGUsIGtleSwgaSkgPT4ge1xuICAgICAgICBub2RlW2tleV0gPSAoYXJnc1tpXSA9PT0gdW5kZWZpbmVkID8gZmllbGRzW2tleV0uZGVmYXVsdCA6IGFyZ3NbaV0pO1xuICAgICAgICByZXR1cm4gbm9kZTtcbiAgICAgIH0sXG4gICAgICB7IGtpbmQ6IHR5cGUgfVxuICAgICk7XG5cbiAgICBmb3IgKGNvbnN0IGtleSBpbiBub2RlKSB7XG4gICAgICB2YWxpZGF0ZShub2RlLCBrZXksIG5vZGVba2V5XSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIG5vZGU7XG4gIH1cblxuICB0W3R5cGVbMF0udG9Mb3dlckNhc2UoKSArIHR5cGUuc2xpY2UoMSldID0gYnVpbGRlcjtcbn1cblxuLyoqXG4gKiBFeGVjdXRlcyB0aGUgZmllbGQgdmFsaWRhdG9ycyBmb3IgYSBnaXZlbiBub2RlXG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkYXRlKG5vZGU/OiBPYmplY3QsIGtleTogc3RyaW5nLCB2YWw6IGFueSkge1xuICBpZiAobm9kZSA9PT0gbnVsbCB8fCB0eXBlb2Ygbm9kZSAhPT0gJ29iamVjdCcpIHtcbiAgICByZXR1cm47XG4gIH1cblxuICBjb25zdCBmaWVsZHMgPSB0Lk5PREVfRklFTERTW25vZGUua2luZF07XG4gIGlmIChmaWVsZHMgPT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNvbnN0IGZpZWxkID0gZmllbGRzW2tleV07XG4gIGlmIChmaWVsZCA9PT0gdW5kZWZpbmVkIHx8IGZpZWxkLnZhbGlkYXRlID09PSB1bmRlZmluZWQpIHtcbiAgICByZXR1cm47XG4gIH1cblxuICBpZiAoZmllbGQub3B0aW9uYWwgJiYgKHZhbCA9PT0gdW5kZWZpbmVkIHx8IHZhbCA9PT0gbnVsbCkpIHtcbiAgICByZXR1cm47XG4gIH1cblxuICBmaWVsZC52YWxpZGF0ZShub2RlLCBrZXksIHZhbCk7XG59XG5cbi8qKlxuICogVGVzdCBpZiBhbiBvYmplY3QgaXMgc2hhbGxvd2x5IGVxdWFsLlxuICovXG5cbmV4cG9ydCBmdW5jdGlvbiBzaGFsbG93RXF1YWwoYWN0dWFsOiBPYmplY3QsIGV4cGVjdGVkOiBPYmplY3QpOiBib29sZWFuIHtcbiAgZm9yIChjb25zdCBrZXkgaW4gZXhwZWN0ZWQpIHtcbiAgICBpZiAoZXhwZWN0ZWQuaGFzT3duUHJvcGVydHkoa2V5KSAmJiBhY3R1YWxba2V5XSAhPT0gZXhwZWN0ZWRba2V5XSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB0cnVlO1xufVxuIl19
277 |
--------------------------------------------------------------------------------
/src/helpers/fetchActions.ts:
--------------------------------------------------------------------------------
1 | import { FetchType } from '../types';
2 |
3 | export const GET_LIST = 'GET_LIST';
4 | export const GET_ONE = 'GET_ONE';
5 | export const GET_MANY = 'GET_MANY';
6 | export const GET_MANY_REFERENCE = 'GET_MANY_REFERENCE';
7 | export const CREATE = 'CREATE';
8 | export const UPDATE = 'UPDATE';
9 | export const UPDATE_MANY = 'UPDATE_MANY';
10 | export const DELETE = 'DELETE';
11 | export const DELETE_MANY = 'DELETE_MANY';
12 |
13 | export const fetchActionsWithRecordResponse = [GET_ONE, CREATE, UPDATE];
14 | export const fetchActionsWithArrayOfIdentifiedRecordsResponse = [
15 | GET_LIST,
16 | GET_MANY,
17 | GET_MANY_REFERENCE,
18 | ];
19 | export const fetchActionsWithArrayOfRecordsResponse = [
20 | ...fetchActionsWithArrayOfIdentifiedRecordsResponse,
21 | UPDATE_MANY,
22 | DELETE_MANY,
23 | ];
24 | export const fetchActionsWithTotalResponse = [GET_LIST, GET_MANY_REFERENCE];
25 |
26 | export const sanitizeFetchType = (fetchType: FetchType) => {
27 | switch (fetchType) {
28 | case GET_LIST:
29 | return 'getList';
30 | case GET_ONE:
31 | return 'getOne';
32 | case GET_MANY:
33 | return 'getMany';
34 | case GET_MANY_REFERENCE:
35 | return 'getManyReference';
36 | case CREATE:
37 | return 'create';
38 | case UPDATE:
39 | return 'update';
40 | case UPDATE_MANY:
41 | return 'updateMany';
42 | case DELETE:
43 | return 'delete';
44 | case DELETE_MANY:
45 | return 'deleteMany';
46 | default:
47 | return fetchType;
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/src/helpers/getArgType.ts:
--------------------------------------------------------------------------------
1 | import * as gqlTypes from '../graphql-ast-types-browser';
2 | import getFinalType from './getFinalType';
3 | import isRequired from './isRequired';
4 | import isList from './isList';
5 |
6 | const getArgType = (arg: any) => {
7 | const type = getFinalType(arg.type);
8 | const required = isRequired(arg.type);
9 | const list = isList(arg.type);
10 |
11 | if (required) {
12 | if (list) {
13 | return gqlTypes.nonNullType(
14 | gqlTypes.listType(
15 | gqlTypes.nonNullType(gqlTypes.namedType(gqlTypes.name(type.name)))
16 | )
17 | );
18 | }
19 |
20 | return gqlTypes.nonNullType(gqlTypes.namedType(gqlTypes.name(type.name)));
21 | }
22 |
23 | if (list) {
24 | return gqlTypes.listType(gqlTypes.namedType(gqlTypes.name(type.name)));
25 | }
26 |
27 | return gqlTypes.namedType(gqlTypes.name(type.name));
28 | };
29 |
30 | export default getArgType;
31 |
--------------------------------------------------------------------------------
/src/helpers/getFinalType.test.ts:
--------------------------------------------------------------------------------
1 | import { TypeKind } from 'graphql';
2 | import getFinalType from './getFinalType';
3 |
4 | describe('getFinalType', () => {
5 | it('returns the correct type for SCALAR types', () => {
6 | expect(getFinalType({ name: 'foo', kind: TypeKind.SCALAR })).toEqual({
7 | name: 'foo',
8 | kind: TypeKind.SCALAR,
9 | });
10 | });
11 | it('returns the correct type for NON_NULL types', () => {
12 | expect(
13 | getFinalType({
14 | kind: TypeKind.NON_NULL,
15 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
16 | })
17 | ).toEqual({
18 | name: 'foo',
19 | kind: TypeKind.SCALAR,
20 | });
21 | });
22 | it('returns the correct type for LIST types', () => {
23 | expect(
24 | getFinalType({
25 | kind: TypeKind.LIST,
26 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
27 | })
28 | ).toEqual({
29 | name: 'foo',
30 | kind: TypeKind.SCALAR,
31 | });
32 | });
33 | it('returns the correct type for NON_NULL LIST types', () => {
34 | expect(
35 | getFinalType({
36 | kind: TypeKind.NON_NULL,
37 | ofType: {
38 | kind: TypeKind.LIST,
39 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
40 | },
41 | })
42 | ).toEqual({ name: 'foo', kind: TypeKind.SCALAR });
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/src/helpers/getFinalType.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IntrospectionType,
3 | IntrospectionTypeRef,
4 | IntrospectionNonNullTypeRef,
5 | TypeKind,
6 | } from 'graphql';
7 |
8 | type GraphQLTypes =
9 | | IntrospectionType
10 | | IntrospectionNonNullTypeRef
11 | | IntrospectionTypeRef;
12 |
13 | /**
14 | * Ensure we get the real type even if the root type is NON_NULL or LIST
15 | * @param {GraphQLType} type
16 | */
17 | const getFinalType = (type: GraphQLTypes): IntrospectionType => {
18 | if (type.kind === TypeKind.NON_NULL || type.kind === TypeKind.LIST) {
19 | return getFinalType(type.ofType);
20 | }
21 |
22 | return type as IntrospectionType;
23 | };
24 |
25 | export default getFinalType;
26 |
--------------------------------------------------------------------------------
/src/helpers/isList.test.ts:
--------------------------------------------------------------------------------
1 | import { TypeKind } from 'graphql';
2 | import isList from './isList';
3 |
4 | describe('isList', () => {
5 | it('returns the correct type for SCALAR types', () => {
6 | expect(isList({ name: 'foo', kind: TypeKind.SCALAR })).toEqual(false);
7 | });
8 | it('returns the correct type for NON_NULL types', () => {
9 | expect(
10 | isList({
11 | kind: TypeKind.NON_NULL,
12 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
13 | })
14 | ).toEqual(false);
15 | });
16 | it('returns the correct type for LIST types', () => {
17 | expect(
18 | isList({
19 | kind: TypeKind.LIST,
20 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
21 | })
22 | ).toEqual(true);
23 | });
24 | it('returns the correct type for NON_NULL LIST types', () => {
25 | expect(
26 | isList({
27 | kind: TypeKind.NON_NULL,
28 | ofType: {
29 | kind: TypeKind.LIST,
30 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
31 | },
32 | })
33 | ).toEqual(true);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/helpers/isList.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IntrospectionType,
3 | IntrospectionTypeRef,
4 | IntrospectionNonNullTypeRef,
5 | TypeKind,
6 | } from 'graphql';
7 |
8 | const isList = (
9 | type: IntrospectionType | IntrospectionNonNullTypeRef | IntrospectionTypeRef
10 | ): boolean => {
11 | if (type.kind === TypeKind.NON_NULL) {
12 | return isList(type.ofType);
13 | }
14 |
15 | return type.kind === TypeKind.LIST;
16 | };
17 |
18 | export default isList;
19 |
--------------------------------------------------------------------------------
/src/helpers/isRequired.test.ts:
--------------------------------------------------------------------------------
1 | import { TypeKind } from 'graphql';
2 | import isRequired from './isRequired';
3 |
4 | describe('isRequired', () => {
5 | it('returns the correct type for SCALAR types', () => {
6 | expect(isRequired({ name: 'foo', kind: TypeKind.SCALAR })).toEqual(false);
7 | });
8 | it('returns the correct type for NON_NULL types', () => {
9 | expect(
10 | isRequired({
11 | kind: TypeKind.NON_NULL,
12 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
13 | })
14 | ).toEqual(true);
15 | });
16 | it('returns the correct type for LIST types', () => {
17 | expect(
18 | isRequired({
19 | kind: TypeKind.LIST,
20 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
21 | })
22 | ).toEqual(false);
23 | });
24 | it('returns the correct type for NON_NULL LIST types', () => {
25 | expect(
26 | isRequired({
27 | kind: TypeKind.NON_NULL,
28 | ofType: {
29 | kind: TypeKind.LIST,
30 | ofType: { name: 'foo', kind: TypeKind.SCALAR },
31 | },
32 | })
33 | ).toEqual(true);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/helpers/isRequired.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IntrospectionType,
3 | IntrospectionListTypeRef,
4 | IntrospectionTypeRef,
5 | TypeKind,
6 | } from 'graphql';
7 |
8 | const isRequired = (
9 | type: IntrospectionType | IntrospectionListTypeRef | IntrospectionTypeRef
10 | ): boolean => {
11 | if (type.kind === TypeKind.LIST) {
12 | return isRequired(type.ofType);
13 | }
14 |
15 | return type.kind === TypeKind.NON_NULL;
16 | };
17 |
18 | export default isRequired;
19 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { buildFields } from './buildGqlQuery/buildFields';
2 | export type { BuildFields } from './buildGqlQuery/buildFields';
3 |
4 | export {
5 | buildArgs,
6 | buildMetaArgs,
7 | buildApolloArgs,
8 | } from './buildGqlQuery/buildArgs';
9 | export type {
10 | BuildArgs,
11 | BuildMetaArgs,
12 | BuildApolloArgs,
13 | } from './buildGqlQuery/buildArgs';
14 |
15 | export { buildGqlQuery } from './buildGqlQuery';
16 | export type { BuildGqlQuery } from './buildGqlQuery';
17 |
18 | export { getResponseParser } from './getResponseParser';
19 | export type { GetResponseParser } from './getResponseParser';
20 |
21 | import buildQuery from './buildQuery';
22 | export { buildQuery };
23 | export type { BuildQuery, BuildQueryFactory } from './buildQuery';
24 |
25 | export { buildVariables, BuildVariables } from './buildVariables';
26 |
27 | export { buildCustomDataProvider as default } from './customDataProvider';
28 | export type { BuildCustomDataProvider } from './customDataProvider';
29 |
30 | export { FetchType } from './types';
31 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IntrospectionObjectType,
3 | IntrospectionSchema,
4 | IntrospectionType,
5 | IntrospectionField,
6 | } from 'graphql';
7 |
8 | export enum FetchType {
9 | GET_LIST = 'GET_LIST',
10 | GET_ONE = 'GET_ONE',
11 | GET_MANY = 'GET_MANY',
12 | GET_MANY_REFERENCE = 'GET_MANY_REFERENCE',
13 | CREATE = 'CREATE',
14 | UPDATE = 'UPDATE',
15 | UPDATE_MANY = 'UPDATE_MANY',
16 | DELETE = 'DELETE',
17 | DELETE_MANY = 'DELETE_MANY',
18 | }
19 |
20 | export type IntrospectedResource = {
21 | type: IntrospectionObjectType;
22 | } & {
23 | [fetchType in FetchType]: IntrospectionField;
24 | };
25 |
26 | export type IntrospectionResult = {
27 | types: IntrospectionType[];
28 | queries: IntrospectionObjectType[];
29 | resources: IntrospectedResource[];
30 | schema: IntrospectionSchema;
31 | };
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | "target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
8 | "allowJs": true /* Allow javascript files to be compiled. */,
9 | "declaration": true /* Generates corresponding '.d.ts' file. */,
10 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
11 | "outDir": "./dist" /* Redirect output structure to the directory. */,
12 |
13 | /* Strict Type-Checking Options */
14 | "strict": true /* Enable all strict type-checking options. */,
15 |
16 | /* Module Resolution Options */
17 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
18 |
19 | /* Advanced Options */
20 | "skipLibCheck": true /* Skip type checking of declaration files. */,
21 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
22 | },
23 | "include": ["src/**/*"],
24 | "exclude": ["node_modules", "**/*.test.ts"]
25 | }
26 |
--------------------------------------------------------------------------------