@smartive/graphql-magic 15.3.1 → 15.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
- ## [15.3.1](https://github.com/smartive/graphql-magic/compare/v15.3.0...v15.3.1) (2024-04-04)
1
+ # [15.4.0](https://github.com/smartive/graphql-magic/compare/v15.3.1...v15.4.0) (2024-04-04)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * **deps:** update docusaurus monorepo to v3.2.0 ([485e127](https://github.com/smartive/graphql-magic/commit/485e127761189bf0c104e6d0a90115d3d21e6c0c))
6
+ * Docs! ([67446c8](https://github.com/smartive/graphql-magic/commit/67446c8a318a851ad86c37d34dca3633962a2b83))
@@ -9,7 +9,7 @@ Let's create a blog with `graphql-magic`!
9
9
  First create a `next.js` website:
10
10
 
11
11
  ```
12
- npx create-next-app@latest magic-blog --ts --app --tailwind --eslint --src
12
+ npx create-next-app@latest magic-blog --ts --app --tailwind --eslint --src-dir
13
13
  cd magic-blog
14
14
  ```
15
15
 
@@ -31,17 +31,17 @@ The most powerful model kind. Entities are models that are stored in database ta
31
31
  }
32
32
  ```
33
33
 
34
- These are the entity options
34
+ These are the entity options:
35
35
 
36
- ### description
36
+ ### `description`
37
37
 
38
- Will appear as graphql description
38
+ Will appear as description in the graphql schema.
39
39
 
40
- ### plural
40
+ ### `plural`
41
41
 
42
42
  `graphql-magic` detects natural language plurals of model names with the `inflection` npm package. You can override this here.
43
43
 
44
- ### creatable
44
+ ### `creatable`
45
45
 
46
46
  When `creatable` is `true`, the entity can be created using a dedicated graphql `create<ModelName>` mutation.
47
47
 
@@ -49,7 +49,7 @@ For this to work, at least one entity field needs to be marked as `creatable`.
49
49
 
50
50
  `creatable` also accepts an object to override properties of the implicitly generated `createdBy` and `createdAt` fields.
51
51
 
52
- ### updatable
52
+ ### `updatable`
53
53
 
54
54
  When `updatable` is `true`, the entity can be created using a dedicated graphql `delete<ModelName>` mutation.
55
55
 
@@ -59,7 +59,7 @@ For this to work, at least one entity field needs to be marked as `updatable`.
59
59
 
60
60
  If a field is updatable, a `<ModelName>Revisions` table is created (containing only the updatable fields) and extended with each update.
61
61
 
62
- ### deletable
62
+ ### `deletable`
63
63
 
64
64
  When `deletable` is `true`, the entity can be created using a dedicated graphql `delete<ModelName>` mutation.
65
65
 
@@ -67,7 +67,7 @@ This is a soft delete (the `deleted` field is set to `true`), and the entity can
67
67
 
68
68
  `deletable` also accepts an object to override properties of the implicitly generated `deleted`, `deletedBy` and `deletedAt` fields.
69
69
 
70
- ### queriable
70
+ ### `queriable`
71
71
 
72
72
  When `queriable` is `true` a graphql `Query` becomes available to fetch exactly one element by id.
73
73
 
@@ -92,7 +92,7 @@ query {
92
92
  }
93
93
  ```
94
94
 
95
- ### listQueriable
95
+ ### `listQueriable`
96
96
 
97
97
  When `listQueriable` is `true` a graphql `Query` becomes available to fetch a list of elements of this model.
98
98
 
@@ -117,20 +117,19 @@ query {
117
117
  }
118
118
  ```
119
119
 
120
- ### displayField
120
+ ### `displayField`
121
121
 
122
122
  The name of the field that ought to be used as display value, e.g. a `Post`'s `title`.
123
123
 
124
- ### defaultOrderBy
124
+ ### `defaultOrderBy`
125
125
 
126
126
  An array of orders with the same structure as the `orderBy` parameters in graphql queries. The implicit default order by is `[{ createdAt: 'DESC }]`.
127
127
 
128
- ### fields
128
+ ### `fields`
129
129
 
130
- An array of fields. See [fields](./fields.md)
130
+ An array of fields. See [fields](./fields)
131
131
 
132
-
133
- ## Scalar
132
+ ## Scalars
134
133
 
135
134
  Used for graphql scalars, e.g.
136
135
 
@@ -141,7 +140,7 @@ Used for graphql scalars, e.g.
141
140
  }
142
141
  ```
143
142
 
144
- ## Enum
143
+ ## Enums
145
144
 
146
145
  An enum that is available as type in the database:
147
146
 
@@ -153,7 +152,7 @@ An enum that is available as type in the database:
153
152
  }
154
153
  ```
155
154
 
156
- ## Raw enum
155
+ ## Raw enums
157
156
 
158
157
  An enum that is *not* available as type in the database:
159
158
 
@@ -165,7 +164,7 @@ An enum that is *not* available as type in the database:
165
164
  }
166
165
  ```
167
166
 
168
- ## Interface
167
+ ## Interfaces
169
168
 
170
169
  Types that can be inherited from, e.g.
171
170
 
@@ -193,7 +192,7 @@ Types that can be inherited from, e.g.
193
192
  }
194
193
  ```
195
194
 
196
- ## Object
195
+ ## Objects
197
196
 
198
197
  Custom types that *don't* correspond to database tables. To be used e.g. as return types for custom resolvers or JSON fields. These can also be used to extend `Query` or `Mutation` which are themselves of that type. E.g.
199
198
 
@@ -237,7 +236,7 @@ query {
237
236
  }
238
237
  ```
239
238
 
240
- ## Input
239
+ ## Inputs
241
240
 
242
241
  A custom input type. To be combined with custom mutations, e.g.
243
242
 
@@ -1,10 +1,91 @@
1
1
  # Fields
2
2
 
3
- Note that some fields are generated implicitly, such as `id`, `createdAt`/`createdBy` if the model is creatable.
3
+ Models of kind `'entity'`, `'object'`, `'interface'` (see the docs on [models](./models)) have an option called `fields`.
4
4
 
5
- ## Fields of fields
5
+ ```ts
6
+ const modelDefinitions: ModelDefinitions = [
7
+ {
8
+ kind: 'entity',
9
+ name: 'User',
10
+ fields: [
11
+ // Fields
12
+ ]
13
+ },
14
+ ]
15
+
16
+ export const models = new Models(modelDefinitions)
17
+ ```
18
+
19
+ ## Kinds
20
+
21
+ Fields can have various kinds, based on the field `kind`:
22
+
23
+ ### Primitive fields
24
+
25
+ Primitive fields are fields where `kind` is either undefined or set to `'primitive'`. They can have the following `type`:
26
+
27
+ * `ID`
28
+ * `Boolean`
29
+ * `String` with optional fields `stringType` and `maxLength`
30
+ * `Int` with optional fields `intType`
31
+ * `Float` with optional fields `floatType`, `double`, `precision`, `scale`
32
+ * `Upload`
33
+
34
+ Examples:
35
+
36
+ ```ts
37
+ {
38
+ name: 'Person',
39
+ fields: [
40
+ {
41
+ type: 'String',
42
+ name: 'name',
43
+ },
44
+ {
45
+ type: 'Int',
46
+ name: 'name',
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ ### Enums
53
+
54
+ When `kind` is `enum`. Requires as `type` the name of a separately defined model of kind `'enum'`. Has optional field `possibleValues` to allow only a subset of available values in mutations.
55
+
56
+ ### Custom
57
+
58
+ When `kind` is `custom`. Requires as `type` the name of a separately defined model of kind `'object'`.
59
+
60
+ If this is an entity field, `graphql-magic` will not try to fetch the result from the database and instead assume the presence of a custom resolver for this field and .
61
+
62
+ ### JSON
63
+
64
+ This kind is only available in entity fields. When `kind` is `json`, `graphql-magic` assumes that this is a `json` column in the database and returns the data as is. The `type` needs be the name of a separately defined model of kind `object` that describes the structure of the `JSON`.
65
+
66
+ ### Relations
67
+
68
+ This kind is only available in entity fields. When `kind` is `relation`, the field describes a link to an entity table. The `type` therefore needs to be the name of a model of kind `'entity'`.
69
+
70
+ ## Options
71
+
72
+ Fields generally have the following options:
73
+
74
+ ### `kind`
75
+
76
+ Fields can have various kinds, which affect other available options. Available kinds:
77
+
78
+ * `undefined` or `'primitive'`
79
+ * `'enum'`
80
+ * `'custom'`
81
+ * `'json'`
82
+ * `'relation'`
6
83
 
7
- Fields generally have the following fields available for configuration:
84
+ For more details, see section on [kinds](#kinds) below.
85
+
86
+ ### `type`
87
+
88
+ This represents the graphql "return type", which can be a primitive or a separate model (depending on the [kind](#kinds)).
8
89
 
9
90
  ### `description`
10
91
 
@@ -244,50 +325,13 @@ If `true` this field will be available in the update mutation for the entity.
244
325
 
245
326
  Also accepts an object that defines a list of `roles` to restrict creation to specific roles.
246
327
 
247
- ## Kinds of fields
248
-
249
- Primitive fields can have various kinds, based on the field `kind`:
250
-
251
- ### Primitive fields
252
-
253
- Primitive fields are fields where `kind` is either undefined or set to `"primitive"`.
254
-
255
- Primitive fields can have various types based on the `type` field:
256
-
257
- * `ID`
258
- * `Boolean`
259
- * `String` with optional fields `stringType` and `maxLength`
260
- * `Int` with optional fields `intType`
261
- * `Float` with optional fields `floatType`, `double`, `precision`, `scale`
262
- * `Upload`
263
-
264
- ### Enums
265
-
266
- When `kind` is `enum`. Requires as `type` the name of a separately defined model of kind `enum`. Has optional field `possibleValues` to allow only a subset of available values in mutations.
267
-
268
- ### Custom
269
-
270
- When `kind` is `custom`. Requires as `type` the name of a separately defined model of kind `object`.
271
-
272
- If this is an entity field, `graphql-magic` will not try to fetch the result from the database and instead assume the presence of a custom resolver for this field and .
273
-
274
- ### JSON
275
-
276
- This kind is only available in entity fields. When `kind` is `json`, `graphql-magic` assumes that this is a `json` column in the database and returns the data as is. The `type` needs be the name of a separately defined model of kind `object` that describes the structure of the `JSON`.
277
-
278
- ### Relations
279
-
280
- This kind is only available in entity fields. When `kind` is `relation`, the field describes a link to an entity table. The `type` therefore needs to be the name of a model of kind `entity`.
281
-
282
- Relation fields accept the following fields:
283
-
284
- ### toOne
328
+ ### `toOne`
285
329
 
286
- If `toOne` is `true` this marks a one-to-one relation, meaning that the reverse relation will not point to an array as is the default.
330
+ Only available on relation fields. If `toOne` is `true` this marks a one-to-one relation, meaning that the reverse relation will not point to an array as is the default.
287
331
 
288
- ### reverse
332
+ ### `reverse`
289
333
 
290
- `graphql-magic` automatically generates a name for the reverse relation, e.g. for a `Comment` pointing to `Post`:
334
+ Only available on relation fiels. `graphql-magic` automatically generates a name for the reverse relation, e.g. for a `Comment` pointing to `Post`:
291
335
 
292
336
  ```ts
293
337
  {
@@ -304,6 +348,6 @@ If `toOne` is `true` this marks a one-to-one relation, meaning that the reverse
304
348
 
305
349
  the reverse relation will automatically be `Post.comments`. With `reverse` this name can be overridden.
306
350
 
307
- ### onDelete
351
+ ### `onDelete`
308
352
 
309
- can be `"cascade"` (default) or `"set-null"`.
353
+ Only available on relation fields. Can be `"cascade"` (default) or `"set-null"`.
@@ -1,17 +1,10 @@
1
1
  # Code generation
2
2
 
3
- Whenever the models have been changed it is necessary generate code use the `graphql-magic` cli.
3
+ `graphql-magic` generates a lot of utility code for you based on the models, in particular typescript types.
4
4
 
5
- This can be done directly with `npx gqm generate`. We recommend to create a `package.json` script and to always generate code after install (or with `npm run generate`):
5
+ This can be done directly with `npx gqm generate`.
6
6
 
7
- ```
8
- "scripts": {
9
- "bootstrap": "npm ci && npm run generate",
10
- "generate": "gqm generate"
11
- }
12
- ```
13
-
14
- First-time this applies the following changes to the repo:
7
+ During the first run, the tool applies the following changes to the repo:
15
8
 
16
9
  * Generate `.gqmrc.json` file.
17
10
  * Add local database connection variables to `.env` file.
@@ -23,9 +16,19 @@ First-time this applies the following changes to the repo:
23
16
  With each application, it generates the following files in the configured "generated" folder:
24
17
 
25
18
  * `schema.graphql` - the schema of the api, for reference
26
- * `models.json` - the final models object including generated fields such as "id"... for reference
19
+ * `models.json` - the final models array, including generated fields such as "id","createdBy"... for reference
27
20
  * `api/index.ts` - the server-side model typescipt types
28
21
  * `client/index.ts` - the client-side typescript types for the provided queries
29
22
  * `client/mutations.ts` - standard mutation queries for all models
30
23
  * `db/index.ts` - types for data from/to the database
31
24
  * `db/knex.ts` - types to extend the `knex` query builder
25
+
26
+ Whenever the models have been changed, it is necessary regenerate this code.
27
+ It is recommended to create a `package.json` script and to always generate code after install (or with `npm run generate`):
28
+
29
+ ```
30
+ "scripts": {
31
+ "bootstrap": "npm ci && npm run generate",
32
+ "generate": "gqm generate"
33
+ }
34
+ ```
@@ -1,12 +1,16 @@
1
1
  # Migrations
2
2
 
3
- After changing the models, if you have an existing database running to compare the models with, you can generate a migration like this:
3
+ Migrations are there to keep the database schema in sync with the models.
4
+
5
+ ## Generating migrations
6
+
7
+ After changing the models with database-relevant changes (and after initial setup), you'll need to have an existing database running to compare the models with, then generate a migration like this:
4
8
 
5
9
  ```
6
10
  npx gqm generate-migration
7
11
  ```
8
12
 
9
- We recommend creating a `package.json` script for this:
13
+ It is recommended to create a `package.json` script for this:
10
14
 
11
15
  ```
12
16
  "generate-migration": "gqm generate-migration"
@@ -14,15 +18,19 @@ We recommend creating a `package.json` script for this:
14
18
 
15
19
  Note: if you are in a `feat/<feature-name>` branch, the script will use that as name for the migration.
16
20
 
17
- This will generate a migration in the `migrations` folder. Check whether it needs to be adapted.
21
+ This will generate a migration file in the `migrations` folder (without running the migration itself yet). Check whether it needs to be adapted.
22
+
23
+ ## Running migrations
24
+
25
+ Migrations themselves are managed with `knex` (see the [knex migration docs](https://knexjs.org/guide/migrations.html)).
18
26
 
19
- You can then run it with `knex` migration commands (using `env-cmd` to add db connection variables in `.env`):
27
+ For example, to migrate to the latest version (using `env-cmd` to add db connection variables in `.env`):
20
28
 
21
29
  ```
22
30
  npx env-cmd knex migrate:latest
23
31
  ```
24
32
 
25
- We recommend creating a `package.json` script and always running it before starting the development server.
33
+ It is recommended to create a `package.json` script and always running it before starting the development server.
26
34
 
27
35
  ```
28
36
  "migrate: "env-cmd knex migrate:latest"
@@ -0,0 +1,242 @@
1
+ # Graphql server
2
+
3
+ ## `executeQuery`
4
+
5
+ `graphql-magic` generates an `execute.ts` file for you, with this structure:
6
+
7
+ ```ts
8
+ import knexConfig from "@/knexfile";
9
+ import { Context, User, execute } from "@smartive/graphql-magic";
10
+ import { randomUUID } from "crypto";
11
+ import { knex } from 'knex';
12
+ import { DateTime } from "luxon";
13
+ import { models } from "../config/models";
14
+
15
+ export const executeGraphql = async <T, V = undefined>(
16
+ body: {
17
+ query: string;
18
+ operationName?: string;
19
+ variables?: V;
20
+ options?: { email?: string };
21
+ }): Promise<{ data: T }> => {
22
+ const db = knex(knexConfig);
23
+ let user: User | undefined;
24
+ // TODO: get user
25
+
26
+ const result = await execute({
27
+ req: null as unknown as Context['req'],
28
+ body,
29
+ knex: db as unknown as Context['knex'],
30
+ locale: 'en',
31
+ locales: ['en'],
32
+ user,
33
+ models: models,
34
+ permissions: { ADMIN: true, UNAUTHENTICATED: true },
35
+ now: DateTime.local(),
36
+ });
37
+ await db.destroy();
38
+
39
+ // https://github.com/vercel/next.js/issues/47447#issuecomment-1500371732
40
+ return JSON.parse(JSON.stringify(result)) as { data: T };
41
+ }
42
+ ```
43
+
44
+ This is where you can set up your graphql server with
45
+
46
+ * user authentication (see the [Tutorial](./tutorial) for an example with auth0)
47
+ * custom resolvers (see [Custom resolvers](#custom-resolvers))
48
+ * mutation hooks (see [Mutation hooks](#mutation-hooks))
49
+
50
+ ## Graphql API
51
+
52
+ If you only need to execute graphql on the server (e.g. on `next.js` server components or server actions), you don't need a graphql endpoint.
53
+ If you need client side querying, use `executeGraphql` to create a graphql endpoint, e.g. in `src/app/api/graphql/route.ts`:
54
+
55
+ ```ts
56
+ export const POST = (req) => {
57
+ return await executeGraphql(req.body)
58
+ }
59
+ ```
60
+
61
+ ## Custom resolvers
62
+
63
+ Sometimes you'll need a custom resolver, at the level of root queries, mutations or existing models. For that you need to create a `additionalResolvers` object.
64
+
65
+ ```ts
66
+ export const additionalResolvers = {
67
+ // custom resolvers go here
68
+ }
69
+ ```
70
+
71
+ Then feed it to `graphql-magic`'s `execute` function:
72
+
73
+ ```ts
74
+ const result = await execute({
75
+ ...
76
+ additionalResolvers
77
+ })
78
+ ```
79
+
80
+ ### Custom queries
81
+
82
+ For that you'll need to extend the `Query` model (and define any needed additional models), e.g.:
83
+
84
+ ```ts
85
+ {
86
+ kind: 'object',
87
+ name: 'Stats',
88
+ fields: [
89
+ {
90
+ name: 'usersCount'
91
+ type: 'Int'
92
+ },
93
+ {
94
+ name: 'postsCount',
95
+ type: 'Int'
96
+ }
97
+ ]
98
+ },
99
+ {
100
+ kind: 'object',
101
+ name: 'Query',
102
+ fields: [
103
+ // You'll need to define a custom resolver for this one
104
+ {
105
+ kind: 'custom',
106
+ name: 'stats',
107
+ type: 'Stats'
108
+ }
109
+ ]
110
+ }
111
+ ```
112
+
113
+ then implement the custom resolver as usual:
114
+
115
+ ```ts
116
+ const additionalResolvers = {
117
+ Query: {
118
+ stats: (parent, args, ctx, schema) => {
119
+ // Implement custom resolver here
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Custom mutations
126
+
127
+ For that you'll need to extend the `Mutation` model (and define any needed additional models), e.g.:
128
+
129
+ ```ts
130
+ {
131
+ kind: 'input',
132
+ name: 'BulkDeleteWhereInput'
133
+ fields: [
134
+ {
135
+ kind: 'ID',
136
+ name: 'ids',
137
+ list: true
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ kind: 'object',
143
+ name: 'Mutation',
144
+ fields: [
145
+ // You'll need to define a custom resolver for this one
146
+ {
147
+ kind: 'custom',
148
+ name: 'bulkDelete',
149
+ args: [
150
+ {
151
+ kind: 'custom',
152
+ name: 'where',
153
+ type: 'BulkDeleteWhereInput'
154
+ }
155
+ ]
156
+ type: 'Boolean'
157
+ }
158
+ ]
159
+ }
160
+ ```
161
+
162
+ then implement the custom resolver as usual:
163
+
164
+ ```ts
165
+ const additionalResolvers = {
166
+ Mutation: {
167
+ bulkDelete: (parent, args, ctx, schema) => {
168
+ // Implement custom resolver here
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### Custom model resolvers
175
+
176
+ Sometimes you need to add a custom field to an existing model. For that, add a field of `kind: 'custom'` to the model.
177
+
178
+ ```ts
179
+ {
180
+ kind: 'entity',
181
+ name: 'User',
182
+ fields: [
183
+ // ...
184
+ {
185
+ kind: 'custom',
186
+ name: 'isAdmin',
187
+ type: 'Boolean'
188
+ }
189
+ ]
190
+ }
191
+ ```
192
+
193
+
194
+ then implement the custom resolver as usual:
195
+
196
+ ```ts
197
+ const additionalResolvers = {
198
+ User: {
199
+ isAdmin: (parent, args, ctx, schema) => {
200
+ // Implement custom resolver here
201
+ }
202
+ }
203
+ }
204
+ ```
205
+
206
+ ## Mutation hooks
207
+
208
+ Sometimes you'll need some custom handling of mutations, before or after the change is committed to the database (e.g. for special data validation, or triggering cleanup work).
209
+
210
+ For this we can implement a global `mutationHook` function that will be called for all mutations with parameters describing the context:
211
+
212
+ ```ts
213
+ import { MutationHook } from '@smartive/graphql-magic';
214
+
215
+ export const mutationHook: MutationHook = (model, action, when, data: { prev, input, normalizedInput, next }, ctx) => {
216
+ switch (model.name) {
217
+ // perform model specific tasks
218
+ }
219
+
220
+ // perform global tasks
221
+ }
222
+ ```
223
+
224
+ Then feed it to `graphql-magic`'s `execute` function:
225
+
226
+ ```ts
227
+ const result = await execute({
228
+ ...
229
+ mutationHook
230
+ })
231
+ ```
232
+
233
+ The mutation hook function takes the following arguments:
234
+
235
+ * `model` the model for the entity being mutated
236
+ * `action`: can be `'create'`, `'update'`, `'delete'` or `'restore'`
237
+ * `when`: either `"before"` or `"after"` the mutation is committed to the database
238
+ * `data` containing the entity in various states:
239
+ * `prev`: the previous entity (undefined in creation mutations)
240
+ * `input`: input from the user
241
+ * `normalizedInput`: input to feed to the database (e.g. including generated values such as `id`, `createdAt`...)
242
+ * `next`: the full next entity after changes are applied
@@ -14,59 +14,60 @@ module.exports = {
14
14
  };
15
15
  ```
16
16
 
17
- ## `executeQuery`
17
+ ## Querying mechanisms
18
18
 
19
- `graphql-magic` generates an `executeQuery` function for you, which you can adapt to your needs.
19
+ ### Server side
20
20
 
21
- It handles things such as:
21
+ On the server side, and with `next.js` server actions, a graphql api becomes unnecessary, and you can execute query directly using `executeQuery`:
22
22
 
23
- * user authentication (see the [Tutorial](./tutorial) for an example with auth0)
24
- * custom resolvers (see [Custom resolvers](./custom-resolvers))
25
- * mutation hooks (see [Mutation hooks](./mutation-hooks))
26
-
27
- TODO
28
-
29
-
30
- ## Queries
23
+ ```tsx
24
+ import { GetMeQuery, GetPostsQuery } from "@/generated/client";
25
+ import { GET_POSTS } from "@/graphql/client/queries/get-posts";
26
+ import { executeGraphql } from "@/graphql/execute";
31
27
 
32
- Typically, you'll put graphql queries in `src/graphql/client/queries`, e.g. `get-posts.ts`
28
+ async function Posts({ me }: { me: GetMeQuery['me'] }) {
29
+ const { data: { posts } } = await executeGraphql<GetPostsQuery>({ query: GET_POSTS })
33
30
 
34
- ```ts
35
- import { gql } from '@smartive/graphql-magic';
36
-
37
- export const GET_POSTS = gql`
38
- query GetPosts {
39
- posts {
40
- id
41
- title
42
- content
43
- createdBy {
44
- username
45
- }
46
- comments {
47
- id
48
- createdBy {
49
- username
50
- }
51
- content
52
- }
53
- }
54
- }
55
- `;
31
+ return <div>
32
+ {posts.map(post => <div key={post.id}>
33
+ <article>
34
+ <h2>{post.title}</h2>
35
+ <div>by {post.createdBy.username}</div>
36
+ <p>{post.content}</p>
37
+ <h4>Comments</h4>
38
+ {post.comments.map(comment => (<div key={comment.id}>
39
+ <div>{comment.createdBy.username}</div>
40
+ <p>{comment.content}</p> by {comment.createdBy.username}
41
+ </div>)
42
+ )}
43
+ </article>
44
+ </div>)}
45
+ </div>
46
+ }
56
47
  ```
57
48
 
58
- The query can then be used like so:
49
+ ### Client side
50
+
51
+ On the client, you'd need to set up a graphql endpoint and then query it like any other graphql api, such as with [`@apollo/client`](https://www.apollographql.com/docs/react/get-started).
59
52
 
60
53
  ```tsx
61
54
  import { GetMeQuery, GetPostsQuery } from "@/generated/client";
62
55
  import { GET_POSTS } from "@/graphql/client/queries/get-posts";
63
56
  import { executeGraphql } from "@/graphql/execute";
57
+ import { gql, useQuery } from '@apollo/client';
64
58
 
65
- async function Posts({ me }: { me: GetMeQuery['me'] }) {
66
- const { data: { posts } } = await executeGraphql<GetPostsQuery>({ query: GET_POSTS })
59
+ function Posts({ me }: { me: GetMeQuery['me'] }) {
60
+ const { loading, error, data } = useQuery<GetPostsQuery>({ query: GET_POSTS })
61
+
62
+ if (loading) {
63
+ return 'Loading...';
64
+ }
65
+ if (error) {
66
+ return `Error! ${error.message}`;
67
+ }
67
68
 
68
69
  return <div>
69
- {posts.map(post => <div key={post.id}>
70
+ {res?.data?.posts.map(post => <div key={post.id}>
70
71
  <article>
71
72
  <h2>{post.title}</h2>
72
73
  <div>by {post.createdBy.username}</div>
@@ -83,11 +84,31 @@ async function Posts({ me }: { me: GetMeQuery['me'] }) {
83
84
  }
84
85
  ```
85
86
 
86
- Client-side: TODO
87
-
88
87
  ## Mutations
89
88
 
90
- Mutation queries are generated by `graphql-magic` directly so you don't need to write them. Use like this:
89
+ Mutation queries are generated by `graphql-magic` directly so you don't need to write them. They have a very simple structure:
90
+
91
+ ```ts
92
+ export const CREATE_POST = gql`
93
+ mutation CreatePostMutation($data: CreatePost!) {
94
+ createPost(data: $data) { id }
95
+ }
96
+ `;
97
+
98
+ export const UPDATE_POST = gql`
99
+ mutation UpdatePostMutation($id: ID!, $data: UpdatePost!) {
100
+ updatePost(where: { id: $id }, data: $data) { id }
101
+ }
102
+ `;
103
+
104
+ export const DELETE_POST = gql`
105
+ mutation DeletePostMutation($id: ID!) {
106
+ deletePost(where: { id: $id })
107
+ }
108
+ `;
109
+ ```
110
+
111
+ Use like this:
91
112
 
92
113
  ```tsx
93
114
  import { CreatePostMutationMutation, CreatePostMutationMutationVariables } from "@/generated/client";
@@ -127,4 +148,4 @@ async function CreatePost() {
127
148
  }
128
149
  ```
129
150
 
130
- Client-side: TODO
151
+ Just like with queries, if is necessary to perform mutations on the client, use a graphql client instead of `executeGraphql`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartive/graphql-magic",
3
- "version": "15.3.1",
3
+ "version": "15.4.0",
4
4
  "description": "",
5
5
  "source": "src/index.ts",
6
6
  "type": "module",
@@ -1,3 +0,0 @@
1
- # Custom resolvers (TODO)
2
-
3
- TODO
@@ -1,3 +0,0 @@
1
- # Hooks (TODO)
2
-
3
- TODO