├── .gitignore ├── CNAME ├── LICENSE ├── README.md ├── docs ├── concepts.md ├── getting-started.md ├── reference.md ├── release-notes.md ├── schema.graphql ├── spec-create.md ├── spec-datasync │ ├── spec-conflict.md │ └── spec-datasync.md ├── spec-delete.md ├── spec-find.md ├── spec-getOne.md ├── spec-overview.md ├── spec-subscriptions.md └── spec-update.md ├── scripts └── ghpages.sh └── website ├── .gitignore ├── createFiles.js ├── docusaurus.config.js ├── package.json ├── sidebars.json ├── src ├── components │ ├── Features │ │ ├── components │ │ │ ├── FeatureContent.jsx │ │ │ ├── FeatureImage.jsx │ │ │ ├── FeatureList.jsx │ │ │ ├── FeaturesHeader.jsx │ │ │ ├── LineConnectors.jsx │ │ │ ├── index.js │ │ │ └── styled.components.js │ │ ├── features.js │ │ ├── index.jsx │ │ ├── styled.components.js │ │ └── styles.module.css │ ├── Hero │ │ ├── animations.js │ │ ├── index.jsx │ │ └── styled.components.js │ ├── Introduction │ │ ├── index.jsx │ │ └── styled.components.js │ ├── UI │ │ ├── Container.jsx │ │ ├── Flex.jsx │ │ ├── Row.jsx │ │ └── index.js │ ├── Video │ │ ├── index.jsx │ │ └── styled.components.js │ └── useWindowSize.jsx ├── css │ └── custom.css └── pages │ ├── index.js │ └── versions.js ├── static ├── .nojekyll ├── CNAME ├── css │ └── custom.css └── img │ ├── aerogear.png │ ├── browser-frame.png │ ├── favicon.ico │ ├── logo.png │ ├── pixel-frame.png │ ├── play.png │ ├── undraw_abstract_x68e.svg │ ├── undraw_code_review.svg │ ├── undraw_contrast.svg │ ├── undraw_data_extraction.svg │ ├── undraw_design.svg │ ├── undraw_online_connection.svg │ ├── undraw_portfolio_update.svg │ ├── undraw_progressive_app.svg │ ├── undraw_redesign.svg │ ├── undraw_usability_testing.svg │ └── undraw_yoga.svg ├── versions.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | graphqlcrud.org -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Licensed under the Apache License, Version 2.0 (the "License"); 190 | you may not use this file except in compliance with the License. 191 | You may obtain a copy of the License at 192 | 193 | http://www.apache.org/licenses/LICENSE-2.0 194 | 195 | Unless required by applicable law or agreed to in writing, software 196 | distributed under the License is distributed on an "AS IS" BASIS, 197 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 198 | See the License for the specific language governing permissions and 199 | limitations under the License. 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # GraphQLCRUD 4 | 5 | GraphQLCRUD is a GraphQL CRUD API specification for databases 6 | 7 | ## Motivation 8 | 9 | GraphQL is a flexible query language supporting many different data access patterns. In practice, simple CRUD operations turn out to be a very common pattern. Standardising this very common pattern enables the community to build tooling specific to the common CRUD style API. 10 | 11 | ## Contributing 12 | 13 | Specification is being build using https://docusaurus.io generator. 14 | Please inspect docs folder for all content 15 | 16 | ## License 17 | 18 | Apache License 2.0 19 | 20 | -------------------------------------------------------------------------------- /docs/concepts.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: concepts 3 | title: Concepts 4 | sidebar_label: Concepts 5 | --- 6 | 7 | ## CRUD methods 8 | 9 | GraphQL CRUD defines different CRUD capabilities that represents 10 | various operations that can be executed on a set of objects: 11 | 12 | - Create: create an object 13 | - Update: update a specific object's properties 14 | - Delete: delete a specific object by its ID 15 | - Get: get a specific object by its ID 16 | - Find: find multiple objects 17 | 18 | ## Input types 19 | 20 | GraphQL CRUD defines common input type categories that can be used in various CRUD methods to define operations. 21 | For example, the Create operation will use a specific input type that does not require an object ID. 22 | 23 | ## Capabilities 24 | 25 | GraphQL CRUD defines different capabilities that developers can enable to modify 26 | what queries can be made against the service. Examples of these capabilities include: 27 | 28 | - Pagination: Ability to paginate content 29 | - Filtering: Ability to peform filtering on specific fields 30 | - Countability: Ability to count the total number of objects 31 | - Consistency: Ability to verify whether a write operation is overriding data 32 | 33 | ## Variations 34 | 35 | Apart from a reference implementation, GraphQL CRUD provides different variations 36 | of the provided queries and mutations that can be used for different needs. 37 | 38 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: gettingstarted 3 | title: Getting Started 4 | sidebar_label: Getting Started 5 | --- 6 | 7 | ## Introduction 8 | 9 | GraphQL CRUD is a superset of the GraphQL specification that gives developers 10 | patterns for building standard, data-driven GraphQL APIs. 11 | GraphQL CRUD is a fully [GraphQL-compliant](http://facebook.github.io/graphql/) specification that provides patterns for access and modification of data. 12 | For example, this GraphQL CRUD query retrieves a single user: 13 | 14 | ```graphql 15 | { 16 | getUser(id: 4) { 17 | name 18 | } 19 | } 20 | ``` 21 | 22 | and returns the following response: 23 | 24 | ```json 25 | { 26 | "user": { 27 | "name": "Mark Zuckerberg" 28 | } 29 | } 30 | ``` 31 | 32 | ## Rationale 33 | 34 | GraphQL is a flexible query language supporting many different data access patterns. 35 | For most projects, CRUD operations turn out to be a very common pattern. Defining this pattern enables the community to build tooling specific to it. 36 | 37 | ## Targets of GraphQL CRUD 38 | 39 | 1. Define a minimal subset of CRUD capabilities 40 | that every application or developer can implement. 41 | 2. Provide an overview of the data access methods that are used to fetch data 42 | 3. Avoid corner cases or specifics of individual service implementations 43 | 4. Define a lenient standard based on practices, existing APIs and providers 44 | that do not enforce specific naming of root fields, types, etc. 45 | 5. Provide a set of the reference implementations for *different programming languages* 46 | 6. Provide capabilities native to GraphQL (no preprocessors, helpers, annotations, etc.) 47 | 48 | ## Non-targets of GraphQL CRUD 49 | 50 | 1. Define every possible method to map database capabilities 51 | 52 | Over the years, we have seen issues with developers adopting very open CRUD capabilities on the client. 53 | There is no silver bullet that will give developers both flexiblity of the query capabilities on the client 54 | and underlying security and control over what data is exposed to the public. 55 | That is why we define only the most common use cases and do not provide a mapping to every capability that a database might expose. 56 | 57 | 2) Include CRUD specifics of any specific platforms 58 | 59 | GraphQL CRUD borrows patterns from existing GraphQL schemas and large GraphQL providers like AWS AppSync and Hasura; 60 | however, it is does not focus on any specific provider itself. 61 | 62 | 3) Defining best practices for writing GraphQL schemas 63 | 64 | GraphQL CRUD focuses only on providing CRUD capabilities. 65 | For general rules for GraphQL schemas see [GraphQL Rules](https://graphql-rules.com) 66 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: reference 3 | title: Reference implementations 4 | sidebar_label: Implementations 5 | --- 6 | 7 | GraphQL CRUD can be applied out of the box using https://graphback.dev 8 | 9 | ## Reference Implementations 10 | 11 | ### Node.js 12 | 13 | [Graphback](https://graphback.dev) provides the ability to generate a schema that will be fully compatible with 14 | GraphQL CRUD and also connect it directly to Postgres, MongoDB and other datasources without writing any code. 15 | 16 | ### JavaScript 17 | 18 | [Offix](https://offix.dev) provides a reference implementation for delta sync queries and implements GraphQL CRUD on the client side. 19 | 20 | 21 | ## Libraries that partialy implement GraphQL CRUD 22 | 23 | This specification was built based on numerous community implementations: 24 | 25 | - AWS AppSync 26 | - Prisma 27 | - Hasura 28 | - PostGraphile 29 | - SQLmancer 30 | - TypeORM 31 | - GraphCMS 32 | - GraphQL CLI 33 | - graphql-serve 34 | - create-graphql 35 | 36 | ## Relation to Relay 37 | 38 | Relay (https://relay.dev/) provides similar capabilites to GraphQL CRUD. 39 | However, GraphQL CRUD takes a more lenient and less verbose approach to building GraphQL queries. 40 | Both standard are incompatibile with each other. 41 | -------------------------------------------------------------------------------- /docs/release-notes.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: release 3 | title: Releases 4 | sidebar_label: Spec Releases 5 | --- 6 | 7 | 8 | ### Early Draft 2020 9 | 10 | - Definition of the concepts and CRUD methods 11 | - Added initial versions without support for different variations -------------------------------------------------------------------------------- /docs/schema.graphql: -------------------------------------------------------------------------------- 1 | 2 | """ @model """ 3 | type Comment { 4 | id: ID! 5 | text: String 6 | description: String 7 | note: Note 8 | } 9 | 10 | input CommentFilter { 11 | id: IDInput 12 | text: StringInput 13 | description: StringInput 14 | noteId: IDInput 15 | and: [CommentFilter!] 16 | or: [CommentFilter!] 17 | not: CommentFilter 18 | } 19 | 20 | type CommentResultList { 21 | items: [Comment]! 22 | offset: Int 23 | limit: Int 24 | count: Int 25 | } 26 | 27 | input CommentSubscriptionFilter { 28 | id: ID 29 | text: String 30 | description: String 31 | } 32 | 33 | input CreateCommentInput { 34 | id: ID 35 | text: String 36 | description: String 37 | noteId: ID 38 | } 39 | 40 | input CreateNoteInput { 41 | id: ID 42 | title: String! 43 | description: String 44 | } 45 | 46 | input IDInput { 47 | ne: ID 48 | eq: ID 49 | le: ID 50 | lt: ID 51 | ge: ID 52 | gt: ID 53 | in: [ID!] 54 | contains: ID 55 | startsWith: ID 56 | endsWith: ID 57 | } 58 | 59 | input MutateCommentInput { 60 | id: ID! 61 | text: String 62 | description: String 63 | noteId: ID 64 | } 65 | 66 | input MutateNoteInput { 67 | id: ID! 68 | title: String 69 | description: String 70 | } 71 | 72 | type Mutation { 73 | createNote(input: CreateNoteInput!): Note! 74 | updateNote(input: MutateNoteInput!): Note! 75 | deleteNote(input: MutateNoteInput!): Note! 76 | createComment(input: CreateCommentInput!): Comment! 77 | updateComment(input: MutateCommentInput!): Comment! 78 | deleteComment(input: MutateCommentInput!): Comment! 79 | } 80 | 81 | """ @model """ 82 | type Note { 83 | id: ID! 84 | title: String! 85 | description: String 86 | 87 | """@oneToMany field: 'note', key: 'noteId'""" 88 | comments(filter: CommentFilter): [Comment]! 89 | } 90 | 91 | input NoteFilter { 92 | id: IDInput 93 | title: StringInput 94 | description: StringInput 95 | and: [NoteFilter!] 96 | or: [NoteFilter!] 97 | not: NoteFilter 98 | } 99 | 100 | type NoteResultList { 101 | items: [Note]! 102 | offset: Int 103 | limit: Int 104 | count: Int 105 | } 106 | 107 | input NoteSubscriptionFilter { 108 | id: ID 109 | title: String 110 | description: String 111 | } 112 | 113 | input OrderByInput { 114 | field: String! 115 | order: SortDirectionEnum = ASC 116 | } 117 | 118 | input PageRequest { 119 | limit: Int 120 | offset: Int 121 | } 122 | 123 | type Query { 124 | getNote(id: ID!): Note 125 | findNotes(filter: NoteFilter, page: PageRequest, orderBy: OrderByInput): NoteResultList! 126 | getComment(id: ID!): Comment 127 | findComments(filter: CommentFilter, page: PageRequest, orderBy: OrderByInput): CommentResultList! 128 | } 129 | 130 | enum SortDirectionEnum { 131 | DESC 132 | ASC 133 | } 134 | 135 | input StringInput { 136 | ne: String 137 | eq: String 138 | le: String 139 | lt: String 140 | ge: String 141 | gt: String 142 | in: [String!] 143 | contains: String 144 | startsWith: String 145 | endsWith: String 146 | } 147 | 148 | type Subscription { 149 | newNote(filter: NoteSubscriptionFilter): Note! 150 | updatedNote(filter: NoteSubscriptionFilter): Note! 151 | deletedNote(filter: NoteSubscriptionFilter): Note! 152 | newComment(filter: CommentSubscriptionFilter): Comment! 153 | updatedComment(filter: CommentSubscriptionFilter): Comment! 154 | deletedComment(filter: CommentSubscriptionFilter): Comment! 155 | } -------------------------------------------------------------------------------- /docs/spec-create.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: create 3 | title: Create Operation 4 | sidebar_label: Create Operation 5 | --- 6 | 7 | ## Create Operation 8 | 9 | The create operation accepts a single input type as argument. 10 | 11 | For example, given a `Note` type like: 12 | 13 | ```graphql 14 | type Note { 15 | id: ID! 16 | title: String! 17 | description: String 18 | comments: [Comment]! 19 | } 20 | ``` 21 | 22 | The following mutation can be used: 23 | 24 | ```graphql 25 | type Mutation { 26 | createNote(input: CreateNoteInput!): Note 27 | } 28 | ``` 29 | 30 | The input type for this create operation looks as follows: 31 | 32 | ```graphql 33 | input CreateNoteInput { 34 | ## To support client side ID creation 35 | id: ID 36 | title: String! 37 | description: String 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/spec-datasync/spec-conflict.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: conflict-resolution 3 | title: Server-Side Conflict Resolution 4 | sidebar_label: Conflict Resolution 5 | --- 6 | 7 | If a client goes offline, there is a strong possiblity that the cached data may not be consistent with the data from the source. Thus it is advisable to have some mechanism for detecting this inconsistency and some way to resolve this. For example, requiring an `updatedAt` timestamp for every mutation is a decent way to ensure this: 8 | 9 | ```graphql 10 | input MutateCommentInput { 11 | id: ID! 12 | title: String 13 | description: String 14 | updatedAt: String! 15 | } 16 | ``` 17 | 18 | This could possibly detect inconsistencies when issuing mutations and possibly resolve them or inform the client about the differences, for example: 19 | 20 | ```json 21 | { 22 | "conflictInfo": { 23 | "serverState": { 24 | "id": "5eedae1367d72e2192561723", 25 | "text": "AlreadyUpdatedTitle", 26 | "_deleted": false, 27 | "createdAt": "1592634899084", 28 | "updatedAt": "1592634899084" 29 | }, 30 | "clientState": { 31 | "id": "5eedae1367d72e2192561723", 32 | "text": "ClientSideUpdate", 33 | "updatedAt": "1592634898093" 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /docs/spec-datasync/spec-datasync.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: delta-queries 3 | title: Delta Query Specification 4 | --- 5 | 6 | Delta Queries extend the GraphQLCRUD spec to support offline-first GraphQL clients. It outlines the features and specifications needed to for smooth offline operation. 7 | 8 | It consists of two aspects: 9 | 10 | - Fetch data that was changed based on a client side `lastChanged` token. 11 | - Ensure data consistency using the aforementioned `lastChanged` token provided. 12 | 13 | ### What are Delta Queries? 14 | 15 | These are a special kind of query that necessarily takes a `lastChanged` argument and for every type that fetches the changed data for that type since the point in time specified by the `lastChanged` argument. An example type definition for this would be: 16 | 17 | ```graphql 18 | type Comment { 19 | id: ID! 20 | text: String 21 | description: String 22 | } 23 | 24 | type Query { 25 | syncComments(lastChanged: String!, filter: CommentFilter): CommentDeltaList! 26 | } 27 | ``` 28 | 29 | In the above example, the delta query is `syncComments` which returns a list of `CommentDelta` type: 30 | 31 | ```graphql 32 | type CommentDelta { 33 | id: ID! 34 | text: String 35 | description: String 36 | createdAt: String 37 | updatedAt: String 38 | _deleted: Boolean 39 | } 40 | 41 | type CommentDeltaList { 42 | items: [CommentDelta]! 43 | lastChanged: String 44 | } 45 | ``` 46 | 47 | Each object of this list is a snapshot of the current state of the row/document in the database, along with the timestamps that show when it was last changed(`updatedAt`), and when it was created(`createdAt`). It also provides info on if the row was deleted(`_deleted`), in which case `updatedAt` says when it was deleted. 48 | 49 | The objects to be fetched in the delta query can also be filtered by using the `filter` argument 50 | which would work exactly like the filter in the [find](./spec-find.md) query. 51 | -------------------------------------------------------------------------------- /docs/spec-delete.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: delete 3 | title: Delete Operation 4 | --- 5 | 6 | ## Delete Operation 7 | 8 | The delete operation accepts a single input type as an argument. 9 | 10 | For example, given a `Note` type like: 11 | 12 | ```graphql 13 | type Note { 14 | id: ID! 15 | title: String! 16 | description: String 17 | comments: [Comment]! 18 | } 19 | ``` 20 | 21 | The following mutation can be used: 22 | 23 | ```graphql 24 | type Mutation { 25 | deleteNote(input: MutateNoteInput!): Note 26 | } 27 | ``` 28 | 29 | The input type for this delete operation looks as follows: 30 | 31 | ```graphql 32 | input MutateNoteInput { 33 | title: String 34 | description: String 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/spec-find.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: find 3 | title: Find Operation 4 | --- 5 | 6 | ## Find Operation 7 | 8 | The find operation allows the client to fetch multiple objects from the database using filtering, sorting and pagination capabilities. 9 | 10 | ## Filtering 11 | 12 | Boolean operators supported when filtering: 13 | - AND 14 | - NOT 15 | - OR 16 | 17 | Matematical operators supported when filtering: 18 | - Not equal: '<>' 19 | - Equal: '=' 20 | - Less than or equal: '<=' 21 | - Less than: '<' 22 | - Greater than or equal: '>=' 23 | - Greater than: '>' 24 | 25 | String opperations supported when filtering: 26 | 27 | - Contains: 'like' 28 | - Starts with: 'like' 29 | - Ends with: 'like' 30 | 31 | Capabilities not supported: 32 | 33 | - Sorting by multiple fields 34 | - Aggregation apart from counting 35 | 36 | ## Example query 37 | 38 | ```graphql 39 | type Query { 40 | findNotes(filter: NoteFilter, orderBy: OrderByInput): [NoteResultList]! 41 | } 42 | ``` 43 | 44 | Input type for `filter` argument: 45 | 46 | ```graphql 47 | input NoteFilter { 48 | id: IDInput 49 | title: StringInput 50 | clickCount: IntInput 51 | floatValue: FloatInput 52 | description: StringInput 53 | and: [NoteFilter!] 54 | or: [NoteFilter!] 55 | not: NoteFilter 56 | } 57 | ``` 58 | 59 | Input type for `orderBy` argument: 60 | 61 | ```graphql 62 | input OrderByInput { 63 | field: String! 64 | order: SortDirectionEnum = ASC 65 | } 66 | 67 | enum SortDirectionEnum { 68 | DESC 69 | ASC 70 | } 71 | ``` 72 | 73 | To enable filtering by specific scalar fields, we can create individual input types for each scalar: 74 | For example, for the five built-in Scalars this could be: 75 | 76 | ```graphql 77 | input StringInput { 78 | ne: String 79 | eq: String 80 | le: String 81 | lt: String 82 | ge: String 83 | gt: String 84 | in: [String!] 85 | contains: String 86 | startsWith: String 87 | endsWith: String 88 | } 89 | 90 | input BooleanInput { 91 | ne: Boolean 92 | eq: Boolean 93 | } 94 | 95 | input FloatInput { 96 | ne: Float 97 | eq: Float 98 | le: Float 99 | lt: Float 100 | ge: Float 101 | gt: Float 102 | in: [Float!] 103 | } 104 | 105 | input IntInput { 106 | ne: Int 107 | eq: Int 108 | le: Int 109 | lt: Int 110 | ge: Int 111 | gt: Int 112 | in: [Int!] 113 | } 114 | 115 | input IDInput { 116 | ne: ID 117 | eq: ID 118 | in: [ID!] 119 | } 120 | ``` 121 | 122 | ## Variations 123 | 124 | ### Pagination 125 | 126 | ```graphql 127 | type Query { 128 | findNotes(filter: NoteFilter, page: PageRequest, orderBy: OrderByInput): NoteResultList! 129 | } 130 | 131 | ## Special type created as wrapper for pagination 132 | type NoteResultList { 133 | items: [Note]! 134 | offset: Int 135 | limit: Int 136 | count: Int 137 | } 138 | 139 | ## Represents page request 140 | input PageRequest { 141 | limit: Int 142 | offset: Int 143 | } 144 | ``` 145 | -------------------------------------------------------------------------------- /docs/spec-getOne.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getOne 3 | title: Get Operation 4 | sidebar_label: Get Operation 5 | --- 6 | 7 | ## Get Operation 8 | 9 | Fetching an object by ID can be enabled by specifying an `id` argument. 10 | The ID can represent any unique field that object has. 11 | 12 | For the Note type, this can be: 13 | 14 | ```graphql 15 | getNote(id: ID!): Note 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/spec-overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: overview 3 | title: Overview 4 | sidebar_label: Overview 5 | --- 6 | 7 | ## Areas covered 8 | 9 | This specification describes all aspects of a flexible GraphQL API suitable for relational databases. 10 | 11 | ## Focus on API 12 | 13 | GraphQLCRUD is a collection of specifications for GraphQL APIs that are abstracting from any database technologies. GraphQLCRUD is concerned with the API only and abstracts away the implementation. As such two implementations of GraphQLCRUD could choose to store data in different ways, but applications interacting with the data through the GraphQLCRUD API wouldn't be able to tell the difference. 14 | 15 | ## Schema Definition Language 16 | 17 | Examples are used throughout this spec to show the final schema generated for a specific data model. In all examples, SDL (Schema Definition Language) notation is used to define the data model. The benefit of SDL is that it is database independent, so we can use the same notation accross all supported databases. 18 | 19 | ## Naming 20 | 21 | GraphQL CRUD does not specify how fields generated for each data type must be named. It is up to each GraphQL CRUD implementation to define a naming system. The reference implementation uses the naming convention as listed in the example queries. 22 | 23 | ## Base schema 24 | 25 | The specification is based on the folllowing two models. 26 | Any additional types will be directly referenced in the schema. 27 | 28 | ```graphql 29 | type Note { 30 | id: ID! 31 | title: String! 32 | description: String 33 | comments: [Comment]! 34 | } 35 | 36 | type Comment { 37 | id: ID! 38 | text: String 39 | description: String 40 | votes: Int 41 | note: Note 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/spec-subscriptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: subscriptions 3 | title: Subscriptions 4 | --- 5 | 6 | ## Subscriptions 7 | 8 | Subscriptions are divided to three different groups of changes: Create, Update and Delete. 9 | 10 | Subscriptions can be used with filtering to only receive events that match the provide filter. 11 | 12 | ```graphql 13 | input NoteSubscriptionFilter { 14 | id: IDInput 15 | title: StringInput 16 | description: StringInput 17 | and: [NoteFilter!] 18 | or: [NoteFilter!] 19 | not: NoteFilter 20 | } 21 | 22 | type Subscription { 23 | newNote(filter: NoteSubscriptionFilter): Note! 24 | updatedNote(filter: NoteSubscriptionFilter): Note! 25 | deletedNote(filter: NoteSubscriptionFilter): Note! 26 | } 27 | ``` -------------------------------------------------------------------------------- /docs/spec-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: update 3 | title: Update Operation 4 | --- 5 | 6 | ## Update Operation 7 | 8 | The update operation accepts a single input type as an argument. 9 | 10 | For example, given a `Note` type like: 11 | 12 | ```graphql 13 | type Note { 14 | id: ID! 15 | title: String! 16 | description: String 17 | comments: [Comment]! 18 | } 19 | ``` 20 | 21 | The following mutation can be used: 22 | 23 | ```graphql 24 | type Mutation { 25 | updateNote(input: MutateNoteInput!): Note 26 | } 27 | ``` 28 | 29 | The input type for this delete operation looks as follows: 30 | 31 | ```graphql 32 | input MutateNoteInput { 33 | # ID field is required for update 34 | id: ID! 35 | title: String 36 | description: String 37 | } 38 | ``` 39 | 40 | ## Variations 41 | 42 | ### Conditional updates 43 | 44 | Conditional updates can be enabled for cases where we want to perform an update 45 | operation only after meeting certain criteria. 46 | 47 | ```graphql 48 | type Mutation { 49 | updateNote(input: MutateNoteInput!, where: UpdateNoteFilter): Note! 50 | } 51 | ``` 52 | 53 | Unlike the previous example, the input type for filtering has all fields marked as optional. 54 | 55 | ```graphql 56 | input UpdateNoteFilter { 57 | title: String 58 | description: String 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /scripts/ghpages.sh: -------------------------------------------------------------------------------- 1 | git checkout gh-pages && 2 | git reset --hard origin/master && 3 | rm README.md && 4 | npm run build && 5 | git add --all && 6 | git commit -a -m"gh-pages update" && 7 | git push origin +gh-pages -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/createFiles.js: -------------------------------------------------------------------------------- 1 | const sideBars = require("./sidebars.json"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | if (sideBars && sideBars.docs) { 6 | for (const chapter of Object.keys(sideBars.docs)) { 7 | for (const element of sideBars.docs[chapter]) { 8 | const text = `--- 9 | id: ${element} 10 | title: ${element} 11 | sidebar_label: ${element} 12 | --- 13 | 14 | ${element} TODO 15 | `; 16 | const pathToFile = path.resolve("../docs/", element + ".md"); 17 | if (!fs.existsSync(pathToFile)) { 18 | fs.writeFileSync(pathToFile, text); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const versions = require("./versions.json"); 3 | 4 | module.exports = { 5 | title: "GraphQLCRUD", 6 | tagline: 7 | "Specification that extends GraphQL with common data access use cases", 8 | url: "https://graphqlcrud.org", 9 | baseUrl: "/", 10 | favicon: "img/logo.png", 11 | 12 | organizationName: "GraphQLCRUD", // Usually your GitHub org/user name. 13 | projectName: "spec", // Usually your repo name. 14 | 15 | themeConfig: { 16 | disableDarkMode: true, 17 | prism: { 18 | theme: require("prism-react-renderer/themes/github"), 19 | defaultLanguage: "javascript", 20 | }, 21 | navbar: { 22 | title: "GraphQLCRUD", 23 | logo: { 24 | alt: "GraphQLCRUD Logo", 25 | src: "img/logo.png", 26 | }, 27 | links: [ 28 | { 29 | to: "docs/next/gettingstarted", 30 | activeBasePath: "docs", 31 | label: "Docs", 32 | position: "left", 33 | // items: [ 34 | // Disable temporarily until we have version of the spec 35 | // { 36 | // label: versions[0], 37 | // to: 'docs/getting-started', 38 | // }, 39 | // ...versions.slice(1).map((version) => ({ 40 | // label: version, 41 | // to: `docs/${version}/getting-started`, 42 | // })), 43 | // { 44 | // label: 'Master/Unreleased', 45 | // to: 'docs/next/getting-started', 46 | // }, 47 | // ], 48 | }, 49 | // { 50 | // to: 'versions', 51 | // label: `v${versions[0]}`, 52 | // position: 'right', 53 | // }, 54 | { 55 | href: "https://github.com/GraphQLCRUD/spec", 56 | label: "GitHub", 57 | position: "right", 58 | }, 59 | ], 60 | }, 61 | footer: { 62 | links: [ 63 | { 64 | title: "Docs", 65 | items: [ 66 | { 67 | label: "Getting Started", 68 | to: "docs/next/gettingstarted", 69 | }, 70 | { 71 | label: "Releases", 72 | to: "docs/release", 73 | }, 74 | ], 75 | }, 76 | { 77 | title: "Community", 78 | items: [ 79 | { 80 | label: "GitHub", 81 | href: "https://github.com/GraphQLCRUD/spec", 82 | }, 83 | { 84 | label: "Discord", 85 | href: "https://discordapp.com/invite/mJ7j84m", 86 | }, 87 | ], 88 | }, 89 | ], 90 | logo: { 91 | alt: "AeroGear Logo", 92 | src: "img/aerogear.png", 93 | href: "https://aerogear.org/", 94 | }, 95 | copyright: `Copyright © ${new Date().getFullYear()} AeroGear`, 96 | }, 97 | }, 98 | presets: [ 99 | [ 100 | "@docusaurus/preset-classic", 101 | { 102 | docs: { 103 | path: "../docs", 104 | routeBasePath: "docs", 105 | sidebarPath: require.resolve("./sidebars.json"), 106 | editUrl: 107 | "https://github.com/aerogear/GraphQLCRUD/edit/master/website/", 108 | }, 109 | theme: { 110 | customCss: require.resolve("./src/css/custom.css"), 111 | }, 112 | }, 113 | ], 114 | ], 115 | }; 116 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqlcrud-docs", 3 | "private": true, 4 | "scripts": { 5 | "start": "docusaurus start", 6 | "build": "docusaurus build", 7 | "swizzle": "docusaurus swizzle", 8 | "deploy": "docusaurus deploy", 9 | "docs": "docusaurus docs:version" 10 | }, 11 | "dependencies": { 12 | "@docusaurus/core": "^2.0.0-alpha.50", 13 | "@docusaurus/preset-classic": "^2.0.0-alpha.50", 14 | "gsap": "^3.2.6", 15 | "react": "^16.8.4", 16 | "react-dom": "^16.8.4", 17 | "scrollmagic": "^2.0.7", 18 | "scrollscene": "^0.0.16", 19 | "styled-components": "^5.1.0" 20 | }, 21 | "version": "0.3.0" 22 | } 23 | -------------------------------------------------------------------------------- /website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Introduction": ["gettingstarted", "concepts", "reference"], 4 | "Specification": [ 5 | "overview", 6 | "create", 7 | "update", 8 | "delete", 9 | "find", 10 | "getOne", 11 | "subscriptions" 12 | ], 13 | "Extensions": [ 14 | { 15 | "Delta Queries": [ 16 | "spec-datasync/delta-queries", 17 | "spec-datasync/conflict-resolution" 18 | ] 19 | } 20 | ], 21 | "Releases": ["release"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /website/src/components/Features/components/FeatureContent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Content, Title } from './styled.components'; 3 | 4 | export const FeatureContent = ({ title, description }) => ( 5 | 6 | {title} 7 |

{description}

8 |
9 | ); -------------------------------------------------------------------------------- /website/src/components/Features/components/FeatureImage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Circle, Image } from './styled.components'; 4 | 5 | export const FeatureImage = React.forwardRef(({ index, imageUrl }, ref) => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }); -------------------------------------------------------------------------------- /website/src/components/Features/components/FeatureList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { gsap } from 'gsap'; 3 | import { ScrollScene } from 'scrollscene'; 4 | 5 | import { FeatureImage } from './FeatureImage'; 6 | import { FeatureContent } from './FeatureContent'; 7 | import { Row } from '../../UI'; 8 | import { FeatureColumn } from './styled.components'; 9 | 10 | function useFeatureAnimation({ left, right, trigger }) { 11 | React.useEffect(() => { 12 | const timeline = gsap.timeline({ paused: true }); 13 | 14 | timeline.to(left.current, { 15 | opacity: 1 16 | }).to(right.current, { 17 | opacity: 1 18 | }); 19 | 20 | new ScrollScene({ 21 | triggerElement: trigger.current, 22 | triggerHook: 0.5, 23 | offset: 100, 24 | duration: 300, 25 | gsap: { 26 | timeline, 27 | }, 28 | }); 29 | }); 30 | } 31 | 32 | const Feature = React.forwardRef((props, ref) => { 33 | const { index } = props; 34 | 35 | const left = React.useRef(); 36 | const right = React.useRef(); 37 | const trigger = React.useRef(); 38 | 39 | useFeatureAnimation({ left, right, trigger }); 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | }); 52 | 53 | export function FeatureList({ features, refs, lineRefs}) { 54 | return features && features.length > 0 && ( 55 | features.map((props, index) => { 56 | return ( 57 | 64 | ); 65 | }) 66 | ) 67 | } -------------------------------------------------------------------------------- /website/src/components/Features/components/FeaturesHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Flex } from '../../UI/Flex'; 3 | import { Content, Title, HR } from './styled.components'; 4 | 5 | export function FeaturesHeader() { 6 | return ( 7 | 8 |
9 | 10 | Features 11 |

12 | GraphQL is a flexible query language supporting many different data access patterns. 13 | In practice, simple CRUD operations turn out to be a very common pattern. Standardising this very common pattern enables the community to build tooling specific to the common CRUD style API. 14 |

15 |
16 |
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/components/Features/components/LineConnectors.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { gsap } from 'gsap'; 3 | import { ScrollScene } from 'scrollscene'; 4 | import { SVG } from './styled.components'; 5 | import { useWindowSize } from '../../useWindowSize'; 6 | 7 | function useLineAnimation({ lineRefs }) { 8 | React.useEffect(() => { 9 | const trigger = document.getElementById('features'); 10 | const timeline = gsap.timeline({ paused: true, duration: 300 }); 11 | 12 | lineRefs.forEach((ref, index) => { 13 | if (ref && ref.current) { 14 | timeline.from(ref.current, { 15 | opacity: 0, 16 | delay: index * 100, 17 | duration: 250 18 | }); 19 | } 20 | }); 21 | 22 | new ScrollScene({ 23 | triggerElement: trigger, 24 | triggerHook: 0.2, 25 | offset: 100, 26 | duration: 250, 27 | gsap: { 28 | timeline, 29 | }, 30 | }); 31 | }) 32 | } 33 | 34 | const Line = React.forwardRef(({ p1, p2 }, ref) => { 35 | const x1 = p1.offsetLeft + (p1.offsetWidth/2); 36 | const x2 = p2.offsetLeft + (p2.offsetWidth/2); 37 | const y1 = p1.offsetTop - (p1.offsetHeight/2); 38 | const y2 = p2.offsetTop - (p2.offsetHeight/2); 39 | return 40 | }); 41 | 42 | export function LineConnectors({ refs, lineRefs }) { 43 | 44 | const [allRefs, setAllRefs] = React.useState([]); 45 | useLineAnimation({ lineRefs }); 46 | useWindowSize(); 47 | 48 | React.useEffect(() => { 49 | setAllRefs(refs); 50 | }); 51 | 52 | return ( 53 | 54 | { 55 | allRefs && allRefs.length && ( 56 | allRefs.map(({ current }, index) => { 57 | const next = allRefs[index+1]; 58 | if (!current || next === undefined) return null; 59 | return ; 60 | }) 61 | ) 62 | } 63 | 64 | ); 65 | }; -------------------------------------------------------------------------------- /website/src/components/Features/components/index.js: -------------------------------------------------------------------------------- 1 | export { FeatureContent } from './FeatureContent'; 2 | export { FeaturesHeader } from './FeaturesHeader'; 3 | export { FeatureList } from './FeatureList'; 4 | export { FeatureImage } from './FeatureImage'; 5 | export { LineConnectors } from './LineConnectors'; 6 | 7 | -------------------------------------------------------------------------------- /website/src/components/Features/components/styled.components.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Title = styled.h3` 4 | font-size: 2rem; 5 | font-weight: 600; 6 | `; 7 | 8 | export const Content = styled.div` 9 | text-align: center; 10 | padding: 6em 0 1em; 11 | `; 12 | 13 | export const HR = styled.hr` 14 | width: 60%; 15 | margin: 4em auto; 16 | `; 17 | 18 | export const Circle = styled.div` 19 | text-align: center; 20 | width: 250px; 21 | height: 250px; 22 | border-radius: 50%; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | background: #f2f2f2; 27 | position: relative; 28 | `; 29 | 30 | export const Image = styled.img` 31 | width: 60%; 32 | margin: 0 auto; 33 | `; 34 | 35 | export const SVG = styled.svg` 36 | margin: 0 auto; 37 | height: 100%; 38 | width: 100%; 39 | position: absolute; 40 | top: 30vh; 41 | left: 0; 42 | z-index: -1; 43 | @media(max-width:966px) { 44 | display: none; 45 | } 46 | `; 47 | 48 | const getOrder = ({ type, index }) => { 49 | if (index%2 === 0) { 50 | return type === 'image' ? 1 : 2; 51 | } 52 | return type === 'image' ? 2 : 1; 53 | } 54 | 55 | export const FeatureColumn = styled.div` 56 | display: flex; 57 | justify-content: center; 58 | align-items: center; 59 | height: 40vh; 60 | width: 50%; 61 | order: ${getOrder}; 62 | opacity: 0; 63 | @media (max-width: 966px) { 64 | width: 100%; 65 | } 66 | `; -------------------------------------------------------------------------------- /website/src/components/Features/features.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const features = [ 4 | { 5 | index: 0, 6 | title: <>Patterns for GraphQL schema, 7 | imageUrl: 'img/undraw_online_connection.svg', 8 | description: ( 9 | <> 10 | Check specification for patterns and pick only those that will really 11 | work for your specific database or business requirements 12 | 13 | ), 14 | }, 15 | { 16 | index: 1, 17 | title: <>Flexibility to adapt, 18 | imageUrl: 'img/undraw_yoga.svg', 19 | description: ( 20 | <> 21 | GraphQL CRUD provides canonical versions and also other variants 22 | giving you overview for different approaches used in the most schemas 23 | 24 | ), 25 | }, 26 | { 27 | index: 2, 28 | title: <>Reference implementations, 29 | imageUrl: 'img/undraw_abstract_x68e.svg', 30 | description: ( 31 | <> 32 | Focus on your business logic and data and generate GraphQL CRUD compliant schemas in any language of your choice 33 | 34 | ), 35 | }, 36 | { 37 | index: 3, 38 | title: <>Framework agnostic, 39 | imageUrl: 'img/undraw_code_review.svg', 40 | description: ( 41 | <> 42 | GraphQL CRUD abstracts from large GraphQL vendors giving you flexibility and ability to migrate without rebuilding your clients and resolves 43 | 44 | ), 45 | }, 46 | { 47 | index: 4, 48 | title: <>Focused on productivity, 49 | imageUrl: 'img/undraw_usability_testing.svg', 50 | description: ( 51 | <> 52 | GraphQL CRUD focuses on productivity by giving developers powerful query capabilities that are not specific to any GraphQL solution providers. 53 | 54 | ), 55 | }, 56 | ]; -------------------------------------------------------------------------------- /website/src/components/Features/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Container } from '../UI'; 4 | import { features } from './features'; 5 | import { Section } from './styled.components'; 6 | import { FeaturesHeader, FeatureList, LineConnectors } from './components'; 7 | 8 | export function Features() { 9 | 10 | const refs = features.map(() => (React.createRef())); 11 | const lineRefs = features.map(() => React.createRef()); 12 | 13 | return ( 14 |
15 | 16 | 17 |
{/* Scroll scene trigger point for scroll event */} 18 | 19 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /website/src/components/Features/styled.components.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Section = styled.section` 4 | position: relative; 5 | min-height: 300vh; 6 | width: 100%; 7 | `; -------------------------------------------------------------------------------- /website/src/components/Features/styles.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * CSS files with the .module.css suffix will be treated as CSS modules 4 | * and scoped locally. 5 | */ 6 | 7 | 8 | @media screen and (max-width: 966px) { 9 | .splitContainer { 10 | height: 100%; 11 | } 12 | .leftSplit, .rightSplit { 13 | width: 100%; 14 | } 15 | 16 | .leftSplit, .splitRow { 17 | height: 50vh; 18 | } 19 | 20 | .splitRow.before, 21 | .splitRow.after { 22 | display: none; 23 | height: 25vh; 24 | } 25 | 26 | } 27 | 28 | /* .circle { 29 | width: 250px; 30 | height: 250px; 31 | border-radius: 50%; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | background: #f2f2f2; 36 | } */ 37 | 38 | .featureImage { 39 | height: 200px; 40 | width: 200px; 41 | margin-bottom: 1rem; 42 | } 43 | 44 | .featureTitle { 45 | font-size: 2rem; 46 | font-weight: 600; 47 | } 48 | 49 | .featureContent { 50 | /* padding: 1.5em; */ 51 | margin: 0 auto; 52 | max-width: 75%; 53 | } -------------------------------------------------------------------------------- /website/src/components/Hero/animations.js: -------------------------------------------------------------------------------- 1 | import TweenLite from 'gsap'; 2 | 3 | export const animations = { 4 | logo({ logo }) { 5 | TweenLite.to(logo, 2, { 6 | opacity: 1, 7 | y: 0, 8 | ease: "elastic.out(1, 0.3)" 9 | }); 10 | }, 11 | title({ title }) { 12 | TweenLite.to(title, 1, { 13 | opacity: 1, 14 | x: 0, 15 | delay: 1 16 | }); 17 | }, 18 | tagline({ tagline }) { 19 | TweenLite.to(tagline, 1, { 20 | opacity: 1, 21 | x: 0, 22 | delay: 1.5 23 | }); 24 | }, 25 | cta({ cta }) { 26 | TweenLite.to(cta, .5, { 27 | opacity: 1, 28 | y: 0, 29 | delay: 2 30 | }); 31 | } 32 | } -------------------------------------------------------------------------------- /website/src/components/Hero/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import useBaseUrl from '@docusaurus/useBaseUrl'; 3 | import Link from '@docusaurus/Link'; 4 | import { animations } from './animations'; 5 | import { Header, HeaderImage, Title, SubTitle, CTA } from './styled.components'; 6 | import { Container } from '../UI'; 7 | 8 | export function Hero({ siteConfig }) { 9 | const logo = useRef(); 10 | const title = useRef(); 11 | const tagline = useRef(); 12 | const cta = useRef(); 13 | 14 | useEffect(() => { 15 | animations.logo({ logo: logo.current }); 16 | animations.title({ title: title.current }); 17 | animations.tagline({ tagline: tagline.current }); 18 | animations.cta({ cta: cta.current }); 19 | }); 20 | 21 | return ( 22 |
23 | 24 | 25 | logo 26 | 27 | GraphQL CRUD 28 | {siteConfig.tagline} 29 | 30 | 33 | Get Started 34 | 35 | 36 | 37 |
38 | ); 39 | } -------------------------------------------------------------------------------- /website/src/components/Hero/styled.components.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Header = styled.div` 4 | height: 100vh; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | text-align: center; 9 | padding: 4rem 0; 10 | `; 11 | 12 | export const HeaderImage = styled.div` 13 | opacity: 0; 14 | max-width: 200px; 15 | width: 60%; 16 | margin: 0 auto; 17 | margin-bottom: 1em; 18 | transform: translateY(300px); 19 | `; 20 | 21 | export const Title = styled.h1` 22 | opacity: 0; 23 | line-height: 7rem; 24 | font-weight: 900; 25 | color: #dc109b ; 26 | background: linear-gradient(60deg, #dc109b, #dc109b, #dc109b); 27 | font-size: 3rem; 28 | -webkit-text-fill-color: transparent; 29 | background-clip: text; 30 | -webkit-background-clip: text; 31 | `; 32 | 33 | export const SubTitle = styled.h2` 34 | opacity: 0; 35 | font-size: 1.5rem; 36 | font-weight: 600; 37 | transform: translateX(50px); 38 | `; 39 | 40 | export const CTA = styled.div` 41 | display: flex; 42 | align-items: center; 43 | justify-content: center; 44 | opacity: 0; 45 | transform: translateY(100); 46 | `; -------------------------------------------------------------------------------- /website/src/components/Introduction/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Flex, Row} from '../UI'; 3 | import { Title, Paragraph, Image } from './styled.components'; 4 | 5 | export function Introduction() { 6 | return ( 7 | 8 | 9 | 10 | 11 |
12 | Standard for exposing data 13 | 14 | GraphQL CRUD provides specification for common operations on top of the GraphQL. 15 | Giving developers out of the box patterns for accessing their data. 16 | Based on the data of public GraphQL APIs and patterns from major GraphQL providers 17 | GraphQL CRUD gives you ultimate guide for your common data access problems without 18 | bringing complexity or limitations to your workflow 19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /website/src/components/Introduction/styled.components.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Title = styled.h2` 4 | margin: 0 auto; 5 | font-weight: 800; 6 | text-transform: uppercase; 7 | @media(min-width:966px) { 8 | width: 80%; 9 | font-size: 3rem; 10 | } 11 | `; 12 | 13 | export const Paragraph = styled.p` 14 | margin-left: auto; 15 | margin-right: auto; 16 | @media(min-width:966px) { 17 | width: 80%; 18 | } 19 | `; 20 | 21 | export const Image = styled.img` 22 | max-width: 400px; 23 | margin: 0 auto; 24 | width: 300px; 25 | @media(min-width:966px) { 26 | width: 500px; 27 | } 28 | `; -------------------------------------------------------------------------------- /website/src/components/UI/Container.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | margin-left: auto; 5 | margin-right: auto; 6 | max-width: ${props => props.maxWidth}; 7 | padding-left: var(--ifm-spacing-horizontal); 8 | padding-right: var(--ifm-spacing-horizontal); 9 | width: 100%; 10 | `; 11 | 12 | Container.defaultProps = { 13 | maxWidth: 'var(--ifm-container-width)' 14 | } 15 | -------------------------------------------------------------------------------- /website/src/components/UI/Flex.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | export const Flex = styled.div` 5 | display: flex; 6 | height: ${props => props.height}; 7 | min-height: ${props => props.minHeight}; 8 | width: ${props => props.width}; 9 | align-items: ${props => props.alignItems}; 10 | justify-content: ${props => props.justifyContent}; 11 | margin: ${props => props.margin}; 12 | background: ${props => props.background}; 13 | color: ${props => props.color}; 14 | order: ${props => props.order}; 15 | `; 16 | 17 | Flex.defaultProps = { 18 | width: '100%', 19 | height: '100vh', 20 | alignItems: 'center', 21 | justifyContent: 'center', 22 | margin: '0 auto' 23 | } -------------------------------------------------------------------------------- /website/src/components/UI/Row.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Row = styled.div` 4 | @media(min-width:966px) { 5 | display: flex; 6 | align-items: center; 7 | flex-flow: row wrap; 8 | } 9 | `; -------------------------------------------------------------------------------- /website/src/components/UI/index.js: -------------------------------------------------------------------------------- 1 | export { Container } from './Container'; 2 | export { Flex } from './Flex'; 3 | export { Row } from './Row'; -------------------------------------------------------------------------------- /website/src/components/Video/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import useBaseUrl from '@docusaurus/useBaseUrl'; 3 | 4 | import { Flex } from '../UI'; 5 | import { 6 | VideoComponent, 7 | Play, 8 | Title, 9 | Content, 10 | Modal, 11 | ModalBackground, 12 | Close, 13 | ModalContent, 14 | YouTube, 15 | IFrame 16 | } from './styled.components'; 17 | 18 | function VideoModal({ open, close}) { 19 | return ( 20 | 21 | 22 | Close 23 | 24 | 25 |