@smartive/graphql-magic 15.2.1 → 15.3.1
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 +2 -2
- package/docs/docs/{tutorial.md → 1-tutorial.md} +26 -30
- package/docs/docs/10-admin-ui.md +3 -0
- package/docs/docs/2-models.md +283 -0
- package/docs/docs/3-fields.md +309 -0
- package/docs/docs/4-generation.md +31 -0
- package/docs/docs/5-migrations.md +31 -0
- package/docs/docs/6-graphql.md +130 -0
- package/docs/docs/7-custom-resolvers.md +3 -0
- package/docs/docs/8-mutation-hooks.md +3 -0
- package/docs/docs/9-polymorphism.md +3 -0
- package/docs/package-lock.json +177 -187
- package/docs/package.json +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
## [15.
|
|
1
|
+
## [15.3.1](https://github.com/smartive/graphql-magic/compare/v15.3.0...v15.3.1) (2024-04-04)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* **deps:** update docusaurus monorepo to v3.2.0 ([485e127](https://github.com/smartive/graphql-magic/commit/485e127761189bf0c104e6d0a90115d3d21e6c0c))
|
|
@@ -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`!
|
|
@@ -19,7 +15,7 @@ cd magic-blog
|
|
|
19
15
|
|
|
20
16
|
Replace `src/app/globals.css`:
|
|
21
17
|
|
|
22
|
-
```
|
|
18
|
+
```css
|
|
23
19
|
@tailwind base;
|
|
24
20
|
@tailwind components;
|
|
25
21
|
@tailwind utilities;
|
|
@@ -71,7 +67,7 @@ label span {
|
|
|
71
67
|
|
|
72
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,7 +132,7 @@ 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
|
|
|
@@ -145,8 +141,8 @@ Enter a migration name, e.g. "setup".
|
|
|
145
141
|
|
|
146
142
|
Run the migration
|
|
147
143
|
|
|
148
|
-
```
|
|
149
|
-
npx env-cmd knex migrate:
|
|
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:
|
|
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";
|
|
@@ -279,7 +275,7 @@ export default async function Home() {
|
|
|
279
275
|
|
|
280
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,14 +326,14 @@ Let's make a blog out of this app 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:
|
|
331
|
+
npx env-cmd knex migrate:latest
|
|
336
332
|
```
|
|
337
333
|
|
|
338
334
|
Create a new query `src/graphql/client/queries/get-posts.ts`:
|
|
339
335
|
|
|
340
|
-
```
|
|
336
|
+
```ts
|
|
341
337
|
import { gql } from '@smartive/graphql-magic';
|
|
342
338
|
|
|
343
339
|
export const GET_POSTS = gql`
|
|
@@ -363,14 +359,14 @@ export const GET_POSTS = gql`
|
|
|
363
359
|
|
|
364
360
|
Generate the new types:
|
|
365
361
|
|
|
366
|
-
```
|
|
362
|
+
```bash
|
|
367
363
|
npx gqm generate
|
|
368
364
|
```
|
|
369
365
|
|
|
370
366
|
Now add all the logic to create and display posts and comments to `src/app/page.tsx`
|
|
371
367
|
|
|
372
368
|
|
|
373
|
-
```
|
|
369
|
+
```tsx
|
|
374
370
|
import { CreateCommentMutationMutation, CreateCommentMutationMutationVariables, CreatePostMutationMutation, CreatePostMutationMutationVariables, GetMeQuery, GetPostsQuery } from "@/generated/client";
|
|
375
371
|
import { CREATE_COMMENT, CREATE_POST } from "@/generated/client/mutations";
|
|
376
372
|
import { GET_ME } from "@/graphql/client/queries/get-me";
|
|
@@ -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
|
+
```
|