@smartive/graphql-magic 15.2.0 → 15.3.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.2.0](https://github.com/smartive/graphql-magic/compare/v15.1.1...v15.2.0) (2024-04-03)
1
+ # [15.3.0](https://github.com/smartive/graphql-magic/compare/v15.2.1...v15.3.0) (2024-04-03)
2
2
 
3
3
 
4
4
  ### Features
5
5
 
6
- * Finish tutorial ([c1b0a21](https://github.com/smartive/graphql-magic/commit/c1b0a215ed23773fd287cd5b5898f07f7c401af1))
6
+ * Docs ([56d96ce](https://github.com/smartive/graphql-magic/commit/56d96ce14c44d486096f6695ebed38a6862c9129))
@@ -1,7 +1,3 @@
1
- ---
2
- sidebar_position: 1
3
- ---
4
-
5
1
  # Tutorial
6
2
 
7
3
  Let's create a blog with `graphql-magic`!
@@ -17,9 +13,9 @@ npx create-next-app@latest magic-blog --ts --app --tailwind --eslint --src
17
13
  cd magic-blog
18
14
  ```
19
15
 
20
- Replace `app/globals.css`:
16
+ Replace `src/app/globals.css`:
21
17
 
22
- ```
18
+ ```css
23
19
  @tailwind base;
24
20
  @tailwind components;
25
21
  @tailwind utilities;
@@ -69,9 +65,9 @@ label span {
69
65
  }
70
66
  ```
71
67
 
72
- Replace `app/page.tsx`:
68
+ Replace `src/app/page.tsx`:
73
69
 
74
- ```
70
+ ```tsx
75
71
  export default async function Home() {
76
72
  return <main>
77
73
  <nav>
@@ -83,7 +79,7 @@ export default async function Home() {
83
79
 
84
80
  Start the website:
85
81
 
86
- ```
82
+ ```bash
87
83
  npm run dev
88
84
  ```
89
85
 
@@ -91,7 +87,7 @@ npm run dev
91
87
 
92
88
  Add this setting to `next.config.mjs`:
93
89
 
94
- ```
90
+ ```ts
95
91
  const nextConfig = {
96
92
  experimental: {
97
93
  serverComponentsExternalPackages: ['knex'],
@@ -101,13 +97,13 @@ const nextConfig = {
101
97
 
102
98
  Install `@smartive/graphql-magic`:
103
99
 
104
- ```
100
+ ```bash
105
101
  npm install @smartive/graphql-magic
106
102
  ```
107
103
 
108
104
  Run the gqm cli:
109
105
 
110
- ```
106
+ ```bash
111
107
  npx gqm generate
112
108
  ```
113
109
 
@@ -116,7 +112,7 @@ npx gqm generate
116
112
  Let's boot a local database instance.
117
113
  Create the following `docker-compose.yml`:
118
114
 
119
- ```
115
+ ```yml
120
116
  version: '3.4'
121
117
  services:
122
118
  postgres:
@@ -136,17 +132,17 @@ Then start it with `docker-compose up`.
136
132
 
137
133
  Generate the first migration:
138
134
 
139
- ```
135
+ ```bash
140
136
  npx gqm generate-migration
141
137
  ```
142
138
 
143
- Enter "setup" as migration name. Or you could first create a `feat/setup` git branch, then it would use that name automatically.
139
+ Enter a migration name, e.g. "setup".
144
140
 
145
141
 
146
142
  Run the migration
147
143
 
148
- ```
149
- npx env-cmd knex migrate:up
144
+ ```bash
145
+ npx env-cmd knex migrate:latest
150
146
  ```
151
147
 
152
148
  ### Auth setup
@@ -156,7 +152,7 @@ For example, follow [this tutorial](https://auth0.com/docs/quickstart/webapp/nex
156
152
 
157
153
  Assuming you used auth0, here's a bare-bones version of what `src/app/page.tsx` could look like:
158
154
 
159
- ```
155
+ ```tsx
160
156
  import { getSession } from '@auth0/nextjs-auth0';
161
157
 
162
158
  export default async function Page() {
@@ -179,7 +175,7 @@ Now, we need to ensure that the user is stored in the database.
179
175
 
180
176
  First extend the user model in `src/config/models.ts` with the following fields:
181
177
 
182
- ```
178
+ ```tsx
183
179
  fields: [
184
180
  {
185
181
  name: 'authId',
@@ -196,25 +192,25 @@ First extend the user model in `src/config/models.ts` with the following fields:
196
192
 
197
193
  The models have changed, generate the new types:
198
194
 
199
- ```
195
+ ```bash
200
196
  npx gqm generate
201
197
  ```
202
198
 
203
199
  Generate the new migration:
204
200
 
205
- ```
201
+ ```bash
206
202
  npx gqm generate-migration
207
203
  ```
208
204
 
209
205
  Edit the generated migration, then run it
210
206
 
211
- ```
212
- npx env-cmd knex migrate:up
207
+ ```bash
208
+ npx env-cmd knex migrate:latest
213
209
  ```
214
210
 
215
211
  Now let's implement the `// TODO: get user` part in the `src/graphql/execute.ts` file
216
212
 
217
- ```
213
+ ```ts
218
214
  const session = await getSession();
219
215
  if (session) {
220
216
  let dbUser = await db('User').where({ authId: session.user.sid }).first();
@@ -235,7 +231,7 @@ Now let's implement the `// TODO: get user` part in the `src/graphql/execute.ts`
235
231
 
236
232
  Extend `src/graphql/client/queries/get-me.ts` to also fetch the user's username:
237
233
 
238
- ```
234
+ ```ts
239
235
  import { gql } from '@smartive/graphql-magic';
240
236
 
241
237
  export const GET_ME = gql`
@@ -250,13 +246,13 @@ export const GET_ME = gql`
250
246
 
251
247
  Generate the new types:
252
248
 
253
- ```
249
+ ```bash
254
250
  npx gqm generate
255
251
  ```
256
252
 
257
253
  Now, let's modify `src/app/page.tsx` so that it fetches the user from the database:
258
254
 
259
- ```
255
+ ```tsx
260
256
  import { GetMeQuery } from "@/generated/client";
261
257
  import { GET_ME } from "@/graphql/client/queries/get-me";
262
258
  import { executeGraphql } from "@/graphql/execute";
@@ -277,9 +273,9 @@ export default async function Home() {
277
273
 
278
274
  ### Content!
279
275
 
280
- Let's create a blog by adding new models in `src/config/models.ts`:
276
+ Let's make a blog out of this app by adding new models in `src/config/models.ts`:
281
277
 
282
- ```
278
+ ```ts
283
279
  {
284
280
  kind: 'entity',
285
281
  name: 'Post',
@@ -330,15 +326,14 @@ Let's create a blog by adding new models in `src/config/models.ts`:
330
326
 
331
327
  Generate and run the new migrations and generate the new models:
332
328
 
333
- ```
329
+ ```bash
334
330
  npx gqm generate-migration
335
- npx env-cmd knex migrate:up
336
- npx gqm generate
331
+ npx env-cmd knex migrate:latest
337
332
  ```
338
333
 
339
334
  Create a new query `src/graphql/client/queries/get-posts.ts`:
340
335
 
341
- ```
336
+ ```ts
342
337
  import { gql } from '@smartive/graphql-magic';
343
338
 
344
339
  export const GET_POSTS = gql`
@@ -362,10 +357,16 @@ export const GET_POSTS = gql`
362
357
  `;
363
358
  ```
364
359
 
360
+ Generate the new types:
361
+
362
+ ```bash
363
+ npx gqm generate
364
+ ```
365
+
365
366
  Now add all the logic to create and display posts and comments to `src/app/page.tsx`
366
367
 
367
368
 
368
- ```
369
+ ```tsx
369
370
  import { CreateCommentMutationMutation, CreateCommentMutationMutationVariables, CreatePostMutationMutation, CreatePostMutationMutationVariables, GetMeQuery, GetPostsQuery } from "@/generated/client";
370
371
  import { CREATE_COMMENT, CREATE_POST } from "@/generated/client/mutations";
371
372
  import { GET_ME } from "@/graphql/client/queries/get-me";
@@ -0,0 +1,3 @@
1
+ # Admin UI (TODO)
2
+
3
+ TODO
@@ -0,0 +1,283 @@
1
+ # Models
2
+
3
+ The source of truth for `graphql-magic` is the `models` object, usually defined in `src/config/models.ts`. This is the minimal models:
4
+
5
+ ```ts
6
+ const modelDefinitions: ModelDefinitions = [
7
+ {
8
+ kind: 'entity',
9
+ name: 'User',
10
+ fields: [
11
+ ]
12
+ },
13
+ ]
14
+
15
+ export const models = new Models(modelDefinitions)
16
+ ```
17
+
18
+ Models can have the following kinds:
19
+
20
+ ## Entities
21
+
22
+ The most powerful model kind. Entities are models that are stored in database tables, and are defined with `kind: 'entity'`:
23
+
24
+ ```ts
25
+ {
26
+ kind: 'entity',
27
+ name: 'Post',
28
+ fields: [
29
+ // ...
30
+ ]
31
+ }
32
+ ```
33
+
34
+ These are the entity options
35
+
36
+ ### description
37
+
38
+ Will appear as graphql description
39
+
40
+ ### plural
41
+
42
+ `graphql-magic` detects natural language plurals of model names with the `inflection` npm package. You can override this here.
43
+
44
+ ### creatable
45
+
46
+ When `creatable` is `true`, the entity can be created using a dedicated graphql `create<ModelName>` mutation.
47
+
48
+ For this to work, at least one entity field needs to be marked as `creatable`.
49
+
50
+ `creatable` also accepts an object to override properties of the implicitly generated `createdBy` and `createdAt` fields.
51
+
52
+ ### updatable
53
+
54
+ When `updatable` is `true`, the entity can be created using a dedicated graphql `delete<ModelName>` mutation.
55
+
56
+ For this to work, at least one entity field needs to be marked as `updatable`.
57
+
58
+ `updatable` also accepts an object to override properties of the implicitly generated `updatedBy` and `updatedAt` fields.
59
+
60
+ If a field is updatable, a `<ModelName>Revisions` table is created (containing only the updatable fields) and extended with each update.
61
+
62
+ ### deletable
63
+
64
+ When `deletable` is `true`, the entity can be created using a dedicated graphql `delete<ModelName>` mutation.
65
+
66
+ This is a soft delete (the `deleted` field is set to `true`), and the entity can be restored with the graphql `restore<ModelName>` mutation.
67
+
68
+ `deletable` also accepts an object to override properties of the implicitly generated `deleted`, `deletedBy` and `deletedAt` fields.
69
+
70
+ ### queriable
71
+
72
+ When `queriable` is `true` a graphql `Query` becomes available to fetch exactly one element by id.
73
+
74
+ For example, with
75
+
76
+ ```ts
77
+ {
78
+ kind: 'entity',
79
+ name: 'Post',
80
+ queriable: true
81
+ ...
82
+ }
83
+ ```
84
+
85
+ the following graphql query becomes possible
86
+
87
+ ```graphql
88
+ query {
89
+ post(where: { id: "bf9496bb-9302-4528-aebc-c97ae49c52fa"}) {
90
+ title
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### listQueriable
96
+
97
+ When `listQueriable` is `true` a graphql `Query` becomes available to fetch a list of elements of this model.
98
+
99
+ For example, with
100
+
101
+ ```ts
102
+ {
103
+ kind: 'entity',
104
+ name: 'Post',
105
+ listQueriable: true
106
+ ...
107
+ }
108
+ ```
109
+
110
+ the following graphql query becomes possible
111
+
112
+ ```graphql
113
+ query {
114
+ posts {
115
+ title
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### displayField
121
+
122
+ The name of the field that ought to be used as display value, e.g. a `Post`'s `title`.
123
+
124
+ ### defaultOrderBy
125
+
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
+
128
+ ### fields
129
+
130
+ An array of fields. See [fields](./fields.md)
131
+
132
+
133
+ ## Scalar
134
+
135
+ Used for graphql scalars, e.g.
136
+
137
+ ```ts
138
+ {
139
+ kind: 'Scalar',
140
+ name: 'DateTime'
141
+ }
142
+ ```
143
+
144
+ ## Enum
145
+
146
+ An enum that is available as type in the database:
147
+
148
+ ```ts
149
+ {
150
+ kind: 'enum',
151
+ name: 'Role',
152
+ values: ['ADMIN', 'MODERATOR', 'USER']
153
+ }
154
+ ```
155
+
156
+ ## Raw enum
157
+
158
+ An enum that is *not* available as type in the database:
159
+
160
+ ```ts
161
+ {
162
+ kind: 'raw-enum',
163
+ name: 'Role',
164
+ values: ['ADMIN', 'MODERATOR', 'USER']
165
+ }
166
+ ```
167
+
168
+ ## Interface
169
+
170
+ Types that can be inherited from, e.g.
171
+
172
+ ```ts
173
+ {
174
+ kind: 'interface',
175
+ name: 'WithContent',
176
+ fields: [
177
+ {
178
+ type: 'String',
179
+ name: 'content'
180
+ }
181
+ ]
182
+ },
183
+ {
184
+ kind: 'entity',
185
+ name: 'Post',
186
+ interfaces: ['WithContent']
187
+ fields: [
188
+ {
189
+ type: 'String',
190
+ name: 'content'
191
+ }
192
+ ]
193
+ }
194
+ ```
195
+
196
+ ## Object
197
+
198
+ 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
+
200
+ ```ts
201
+ {
202
+ kind: 'object',
203
+ name: 'Stats',
204
+ fields: [
205
+ {
206
+ name: 'usersCount'
207
+ type: 'Int'
208
+ },
209
+ {
210
+ name: 'postsCount',
211
+ type: 'Int'
212
+ }
213
+ ]
214
+ },
215
+ {
216
+ kind: 'object',
217
+ name: 'Query',
218
+ fields: [
219
+ // You'll need to define a custom resolver for this one
220
+ {
221
+ kind: 'custom',
222
+ name: 'stats',
223
+ type: 'Stats'
224
+ }
225
+ ]
226
+ }
227
+ ```
228
+
229
+ will make this query possible:
230
+
231
+ ```graphql
232
+ query {
233
+ stats {
234
+ usersCount
235
+ postsCount
236
+ }
237
+ }
238
+ ```
239
+
240
+ ## Input
241
+
242
+ A custom input type. To be combined with custom mutations, e.g.
243
+
244
+ ```ts
245
+ {
246
+ kind: 'input',
247
+ name: 'BulkDeleteWhereInput'
248
+ fields: [
249
+ {
250
+ kind: 'ID',
251
+ name: 'ids',
252
+ list: true
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ kind: 'object',
258
+ name: 'Mutation',
259
+ fields: [
260
+ // You'll need to define a custom resolver for this one
261
+ {
262
+ kind: 'custom',
263
+ name: 'bulkDelete',
264
+ args: [
265
+ {
266
+ kind: 'custom',
267
+ name: 'where',
268
+ type: 'BulkDeleteWhereInput'
269
+ }
270
+ ]
271
+ type: 'Boolean'
272
+ }
273
+ ]
274
+ }
275
+ ```
276
+
277
+ will make this mutation possible:
278
+
279
+ ```graphql
280
+ mutation {
281
+ bulkDelete(where: { ids: [...]})
282
+ }
283
+ ```
@@ -0,0 +1,309 @@
1
+ # Fields
2
+
3
+ Note that some fields are generated implicitly, such as `id`, `createdAt`/`createdBy` if the model is creatable.
4
+
5
+ ## Fields of fields
6
+
7
+ Fields generally have the following fields available for configuration:
8
+
9
+ ### `description`
10
+
11
+ Will appear as description in the graphql schema.
12
+
13
+ ### `list`
14
+
15
+ If `list` is `true` the result is an array.
16
+
17
+ ### `nonNull`
18
+
19
+ Will make the field required both in the graphql schema and in the database.
20
+
21
+ ### `defaultValue`
22
+
23
+ Will set this as default value in graphql mutations and in the database.
24
+
25
+ ### `args`
26
+
27
+ An array of fields that can then be used as parameters, e.g. if this field is implemented as a custom resolver.
28
+
29
+ ### `directives`
30
+
31
+ Graphql directives for this field.
32
+
33
+ ### `primary`
34
+
35
+ If `true` this will generate a primary key in the database.
36
+
37
+ ### `unique`
38
+
39
+ If `true` this will generate a unique key in the database.
40
+
41
+ ### `filterable`
42
+
43
+ If true, this field will be available in the `where` parameter for queries of this entity.
44
+
45
+ E.g. with
46
+
47
+ ```ts
48
+ {
49
+ name: 'Post',
50
+ fields: [
51
+ {
52
+ name: 'name',
53
+ type: 'String',
54
+ filterable: true
55
+ }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ this becomes possible:
61
+
62
+ ```graphql
63
+ query {
64
+ posts(where: { name: "Hello World" }) {
65
+ title
66
+ }
67
+ }
68
+ ```
69
+
70
+ With relations, this enables sub-filters, e.g. with
71
+
72
+ ```ts
73
+ {
74
+ name: 'Comment',
75
+ fields: [
76
+ {
77
+ kind: 'relation',
78
+ name: 'post',
79
+ type: 'Post',
80
+ filterable: true
81
+ }
82
+ ]
83
+ }
84
+ ```
85
+
86
+ this becomes possible:
87
+
88
+ ```graphql
89
+ query {
90
+ comments(where: { post: { name: "Hello World" } }) {
91
+ content
92
+ }
93
+ }
94
+ ```
95
+
96
+
97
+ ### `reverseFilterable`
98
+
99
+ Only relevant on relation fields. On `true` makes the reverse relation filterable.
100
+
101
+ E.g. with
102
+
103
+ ```ts
104
+ {
105
+ name: 'Comment',
106
+ fields: [
107
+ {
108
+ name: 'post',
109
+ type: 'String',
110
+ reverseFilterable: true
111
+ }
112
+ ]
113
+ }
114
+ ```
115
+
116
+ this becomes possible:
117
+
118
+ ```graphql
119
+ query {
120
+ posts(where: { comments_SOME: { name: "Hello World" } }) {
121
+ title
122
+ }
123
+ }
124
+ ```
125
+
126
+ Available filter postfixes are `_SOME` and `_NONE`.
127
+
128
+ ### `searchable`
129
+
130
+ On `true` makes the field searchable. Search always happens across all fields marked as searchable (only one has to match). Search is case insensitive.
131
+
132
+ E.g. with
133
+
134
+ ```ts
135
+ {
136
+ name: 'Post',
137
+ fields: [
138
+ {
139
+ name: 'title',
140
+ type: 'String',
141
+ searchable: true
142
+ },
143
+ {
144
+ name: 'content',
145
+ type: 'String',
146
+ searchable: true
147
+ }
148
+
149
+ ]
150
+ }
151
+ ```
152
+
153
+ this becomes possible:
154
+
155
+ ```graphql
156
+ query {
157
+ posts(search: "Hello") {
158
+ title
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### `orderable`
164
+
165
+ On `true` makes the field available to the `orderBy` parameter.
166
+
167
+ E.g. with
168
+
169
+ ```ts
170
+ {
171
+ name: 'Post',
172
+ fields: [
173
+ {
174
+ name: 'title',
175
+ type: 'String',
176
+ orderable: true
177
+ },
178
+ ]
179
+ }
180
+ ```
181
+
182
+ this becomes possible:
183
+
184
+ ```graphql
185
+ query {
186
+ posts(orderBy: [{ title: DESC }]) {
187
+ title
188
+ }
189
+ }
190
+ ```
191
+
192
+
193
+ ### `comparable`
194
+
195
+ On `true` makes the field comparable.
196
+
197
+ E.g. with
198
+
199
+ ```ts
200
+ {
201
+ name: 'Post',
202
+ fields: [
203
+ {
204
+ name: 'rating',
205
+ type: 'Int',
206
+ comparable: true
207
+ },
208
+ ]
209
+ }
210
+ ```
211
+
212
+ this becomes possible:
213
+
214
+ ```graphql
215
+ query {
216
+ posts(where: { rating_GTE 4 }) {
217
+ title
218
+ }
219
+ }
220
+ ```
221
+
222
+ Available postfixes are:
223
+
224
+ * `_GT`: greater than
225
+ * `_GTE`: greater than or equal
226
+ * `_LT`: less than
227
+ * `_LTE`: less than or equal
228
+
229
+ ### `queriable`
230
+
231
+ `true` by default. If explicitly set to `false`, the field won't be queriable via graphql.
232
+
233
+ Also accepts an object that defines a list of `roles` to restrict access to specific roles.
234
+
235
+ ### `creatable`
236
+
237
+ If `true` this field will be available in the create mutation for the entity.
238
+
239
+ Also accepts an object that defines a list of `roles` to restrict creation to specific roles.
240
+
241
+ ### `updatable`
242
+
243
+ If `true` this field will be available in the update mutation for the entity.
244
+
245
+ Also accepts an object that defines a list of `roles` to restrict creation to specific roles.
246
+
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
285
+
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.
287
+
288
+ ### reverse
289
+
290
+ `graphql-magic` automatically generates a name for the reverse relation, e.g. for a `Comment` pointing to `Post`:
291
+
292
+ ```ts
293
+ {
294
+ name: 'Comment',
295
+ fields: [
296
+ {
297
+ kind: 'relation',
298
+ name: 'post',
299
+ type: 'Post'
300
+ }
301
+ ]
302
+ }
303
+ ```
304
+
305
+ the reverse relation will automatically be `Post.comments`. With `reverse` this name can be overridden.
306
+
307
+ ### onDelete
308
+
309
+ can be `"cascade"` (default) or `"set-null"`.
@@ -0,0 +1,31 @@
1
+ # Code generation
2
+
3
+ Whenever the models have been changed it is necessary generate code use the `graphql-magic` cli.
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`):
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:
15
+
16
+ * Generate `.gqmrc.json` file.
17
+ * Add local database connection variables to `.env` file.
18
+ * Add generated folder to `.gitignore`
19
+ * Generate `models.ts` file (if not present).
20
+ * Generate a basic `get-me.ts` example graphql query.
21
+ * Generate the `execute.ts` file for the execution
22
+
23
+ With each application, it generates the following files in the configured "generated" folder:
24
+
25
+ * `schema.graphql` - the schema of the api, for reference
26
+ * `models.json` - the final models object including generated fields such as "id"... for reference
27
+ * `api/index.ts` - the server-side model typescipt types
28
+ * `client/index.ts` - the client-side typescript types for the provided queries
29
+ * `client/mutations.ts` - standard mutation queries for all models
30
+ * `db/index.ts` - types for data from/to the database
31
+ * `db/knex.ts` - types to extend the `knex` query builder
@@ -0,0 +1,31 @@
1
+ # Migrations
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:
4
+
5
+ ```
6
+ npx gqm generate-migration
7
+ ```
8
+
9
+ We recommend creating a `package.json` script for this:
10
+
11
+ ```
12
+ "generate-migration": "gqm generate-migration"
13
+ ```
14
+
15
+ Note: if you are in a `feat/<feature-name>` branch, the script will use that as name for the migration.
16
+
17
+ This will generate a migration in the `migrations` folder. Check whether it needs to be adapted.
18
+
19
+ You can then run it with `knex` migration commands (using `env-cmd` to add db connection variables in `.env`):
20
+
21
+ ```
22
+ npx env-cmd knex migrate:latest
23
+ ```
24
+
25
+ We recommend creating a `package.json` script and always running it before starting the development server.
26
+
27
+ ```
28
+ "migrate: "env-cmd knex migrate:latest"
29
+ "predev: "npm run migrate"
30
+ "dev": "next dev"
31
+ ```
@@ -0,0 +1,130 @@
1
+ # Graphql querying
2
+
3
+ For autocompletion of your queries, you can create the following `apollo.config.js` file:
4
+
5
+ ```ts
6
+ module.exports = {
7
+ client: {
8
+ service: {
9
+ name: 'your-project',
10
+ localSchemaFile: './src/generated/schema.graphql',
11
+ },
12
+ includes: ['./src/**/*.ts', './src/**/*.tsx'],
13
+ },
14
+ };
15
+ ```
16
+
17
+ ## `executeQuery`
18
+
19
+ `graphql-magic` generates an `executeQuery` function for you, which you can adapt to your needs.
20
+
21
+ It handles things such as:
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
31
+
32
+ Typically, you'll put graphql queries in `src/graphql/client/queries`, e.g. `get-posts.ts`
33
+
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
+ `;
56
+ ```
57
+
58
+ The query can then be used like so:
59
+
60
+ ```tsx
61
+ import { GetMeQuery, GetPostsQuery } from "@/generated/client";
62
+ import { GET_POSTS } from "@/graphql/client/queries/get-posts";
63
+ import { executeGraphql } from "@/graphql/execute";
64
+
65
+ async function Posts({ me }: { me: GetMeQuery['me'] }) {
66
+ const { data: { posts } } = await executeGraphql<GetPostsQuery>({ query: GET_POSTS })
67
+
68
+ return <div>
69
+ {posts.map(post => <div key={post.id}>
70
+ <article>
71
+ <h2>{post.title}</h2>
72
+ <div>by {post.createdBy.username}</div>
73
+ <p>{post.content}</p>
74
+ <h4>Comments</h4>
75
+ {post.comments.map(comment => (<div key={comment.id}>
76
+ <div>{comment.createdBy.username}</div>
77
+ <p>{comment.content}</p> by {comment.createdBy.username}
78
+ </div>)
79
+ )}
80
+ </article>
81
+ </div>)}
82
+ </div>
83
+ }
84
+ ```
85
+
86
+ Client-side: TODO
87
+
88
+ ## Mutations
89
+
90
+ Mutation queries are generated by `graphql-magic` directly so you don't need to write them. Use like this:
91
+
92
+ ```tsx
93
+ import { CreatePostMutationMutation, CreatePostMutationMutationVariables } from "@/generated/client";
94
+ import { CREATE_POST } from "@/generated/client/mutations";
95
+ import { executeGraphql } from "@/graphql/execute";
96
+ import { revalidatePath } from "next/cache";
97
+
98
+ async function CreatePost() {
99
+ async function createPost(formData: FormData) {
100
+ 'use server'
101
+ await executeGraphql<CreatePostMutationMutation, CreatePostMutationMutationVariables>({
102
+ query: CREATE_POST,
103
+ variables: {
104
+ data: {
105
+ title: formData.get('title') as string,
106
+ content: formData.get('content') as string
107
+ }
108
+ }
109
+ })
110
+ revalidatePath('/')
111
+ }
112
+
113
+ return <form action={createPost}>
114
+ <h2>New Post</h2>
115
+ <label>
116
+ <span>Title</span>
117
+ <input name="title" />
118
+ </label>
119
+ <label>
120
+ <span>Content</span>
121
+ <textarea rows={5} name="content" />
122
+ </label>
123
+ <div>
124
+ <button type="submit">Create</button>
125
+ </div>
126
+ </form>
127
+ }
128
+ ```
129
+
130
+ Client-side: TODO
@@ -0,0 +1,3 @@
1
+ # Custom resolvers (TODO)
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Hooks (TODO)
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Polymorphism (TODO)
2
+
3
+ TODO
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartive/graphql-magic",
3
- "version": "15.2.0",
3
+ "version": "15.3.0",
4
4
  "description": "",
5
5
  "source": "src/index.ts",
6
6
  "type": "module",