@smartive/graphql-magic 15.0.0 → 15.1.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.
@@ -22,3 +22,25 @@ jobs:
22
22
  env:
23
23
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24
24
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
25
+ docs:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v2
29
+ with:
30
+ persist-credentials: false
31
+
32
+ - name: Setup Node
33
+ uses: actions/setup-node@v3
34
+ with:
35
+ node-version: '20'
36
+ - name: Install and Build
37
+ run: |
38
+ cd docs
39
+ npm install
40
+ npm run build
41
+ - name: Deploy
42
+ uses: JamesIves/github-pages-deploy-action@v4
43
+ with:
44
+ branch: gh-pages
45
+ folder: docs/build
46
+ token: ${{ secrets.GITHUB_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1 +1,6 @@
1
- # [15.0.0](https://github.com/smartive/graphql-magic/compare/v14.1.0...v15.0.0) (2024-04-03)
1
+ # [15.1.0](https://github.com/smartive/graphql-magic/compare/v15.0.1...v15.1.0) (2024-04-03)
2
+
3
+
4
+ ### Features
5
+
6
+ * Docs ([#153](https://github.com/smartive/graphql-magic/issues/153)) ([a827813](https://github.com/smartive/graphql-magic/commit/a8278137bf7d8b68a06d17ebeb9380071490b4c9))
package/README.md CHANGED
@@ -2,42 +2,7 @@
2
2
 
3
3
  Welcome to graphql-magic!
4
4
 
5
- ## Usage
6
-
7
- ### Prerequisites
8
-
9
- * Next.js
10
- * TypeScript
11
- * Knex.js
12
- * Postgresql
13
-
14
- ### Setup
15
-
16
- Dependencies:
17
-
18
- ```
19
- npm i @smartive/graphql-magic
20
- ```
21
-
22
- Setup:
23
-
24
- ```
25
- npx gqm setup
26
- ```
27
-
28
- Generate all the things:
29
-
30
- ```
31
- npx gqm generate
32
- ```
33
-
34
- Generate a migration:
35
-
36
- ```
37
- npx gqm generate-migration
38
- ```
39
-
40
- To be continued...
5
+ See the [docs](https://)
41
6
 
42
7
  ## Development
43
8
 
package/dist/bin/gqm.cjs CHANGED
@@ -1656,6 +1656,42 @@ export const GET_ME = gql\`
1656
1656
  }
1657
1657
  \`;
1658
1658
  `;
1659
+ var EXECUTE = `
1660
+ import knexConfig from "@/knexfile";
1661
+ import { Context, User, execute } from "@smartive/graphql-magic";
1662
+ import { randomUUID } from "crypto";
1663
+ import { knex } from 'knex';
1664
+ import { DateTime } from "luxon";
1665
+ import { models } from "../config/models";
1666
+
1667
+ export const executeGraphql = async <T, V = undefined>(
1668
+ body: {
1669
+ query: string;
1670
+ operationName?: string;
1671
+ variables?: V;
1672
+ options?: { email?: string };
1673
+ }): Promise<{ data: T }> => {
1674
+ const db = knex(knexConfig);
1675
+ let user: User | undefined;
1676
+ // TODO: get user
1677
+
1678
+ const result = await execute({
1679
+ req: null as unknown as Context['req'],
1680
+ body,
1681
+ knex: db as unknown as Context['knex'],
1682
+ locale: 'en',
1683
+ locales: ['en'],
1684
+ user,
1685
+ models: models,
1686
+ permissions: { ADMIN: true, UNAUTHENTICATED: true },
1687
+ now: DateTime.local(),
1688
+ });
1689
+ await db.destroy();
1690
+
1691
+ // https://github.com/vercel/next.js/issues/47447#issuecomment-1500371732
1692
+ return JSON.parse(JSON.stringify(result)) as { data: T };
1693
+ }
1694
+ `;
1659
1695
 
1660
1696
  // src/bin/gqm/settings.ts
1661
1697
  var SETTINGS_PATH = ".gqmrc.json";
@@ -1696,10 +1732,11 @@ var DEFAULTS = {
1696
1732
  }
1697
1733
  },
1698
1734
  graphqlQueriesPath: {
1699
- question: "Where to look for graphql queries?",
1700
- defaultValue: "src/graphql/client/queries",
1735
+ question: "Where to put graphql code?",
1736
+ defaultValue: "src/graphql",
1701
1737
  init: (path) => {
1702
- ensureFileExists(`${path}/get-me.ts`, GET_ME);
1738
+ ensureFileExists(`${path}/client/queries/get-me.ts`, GET_ME);
1739
+ ensureFileExists(`${path}/execute.ts`, EXECUTE);
1703
1740
  }
1704
1741
  },
1705
1742
  gqlModule: {
@@ -1766,7 +1803,7 @@ var ensureFileExists = (filePath, content) => {
1766
1803
  }
1767
1804
  };
1768
1805
  var ensureFileContains = (filePath, content, fallback) => {
1769
- ensureFileExists(filePath, content);
1806
+ ensureFileExists(filePath, "");
1770
1807
  const fileContent = (0, import_fs.readFileSync)(filePath, "utf-8");
1771
1808
  if (!fileContent.includes(content)) {
1772
1809
  (0, import_fs.writeFileSync)(filePath, fileContent + (fallback ?? content));
package/docs/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Website
2
+
3
+ This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
4
+
5
+ ### Installation
6
+
7
+ ```
8
+ $ yarn
9
+ ```
10
+
11
+ ### Local Development
12
+
13
+ ```
14
+ $ yarn start
15
+ ```
16
+
17
+ This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18
+
19
+ ### Build
20
+
21
+ ```
22
+ $ yarn build
23
+ ```
24
+
25
+ This command generates static content into the `build` directory and can be served using any static contents hosting service.
26
+
27
+ ### Deployment
28
+
29
+ Using SSH:
30
+
31
+ ```
32
+ $ USE_SSH=true yarn deploy
33
+ ```
34
+
35
+ Not using SSH:
36
+
37
+ ```
38
+ $ GIT_USER=<Your GitHub username> yarn deploy
39
+ ```
40
+
41
+ If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3
+ };
@@ -0,0 +1,431 @@
1
+ ---
2
+ sidebar_position: 1
3
+ ---
4
+
5
+ # Tutorial
6
+
7
+ Let's create a blog with `graphql-magic`!
8
+
9
+ ## Setup
10
+
11
+ ### Code base
12
+
13
+ First create a next.js website:
14
+
15
+ ```
16
+ npx create-next-app@latest magic-blog --ts --app --tailwind --eslint --src
17
+ cd magic-blog
18
+ ```
19
+
20
+ For some styling install `preline` and dependencies:
21
+
22
+ ```
23
+ npm i preline @tailwindcss/forms
24
+ ```
25
+
26
+ Add `@tailwindcss/forms` to `tailwind.config.ts`:
27
+
28
+ ```
29
+ plugins: [
30
+ require('@tailwindcss/forms'),
31
+ ],
32
+ ```
33
+
34
+ Replace `app/globals.css`:
35
+
36
+ ```
37
+ TODO
38
+ ```
39
+
40
+ Replace `app/layout.tsx`:
41
+
42
+ ```
43
+ export default function RootLayout({
44
+ children,
45
+ }: Readonly<{
46
+ children: React.ReactNode;
47
+ }>) {
48
+ return (
49
+ <html>
50
+ <body>{children}</body>
51
+ </html>
52
+ );
53
+ }
54
+ ```
55
+
56
+ Replace `app/page.tsx`:
57
+
58
+ ```
59
+ export default function Page() {
60
+ return <div>
61
+ <h1>Magic Blog</h1>
62
+ </div>
63
+ }
64
+ ```
65
+
66
+ Add this setting to `next.config.mjs`:
67
+
68
+ ```
69
+ const nextConfig = {
70
+ experimental: {
71
+ serverComponentsExternalPackages: ['knex'],
72
+ }
73
+ };
74
+ ```
75
+
76
+ Install `@smartive/graphql-magic`:
77
+
78
+ ```
79
+ npm install @smartive/graphql-magic
80
+ ```
81
+
82
+ Temporary:
83
+
84
+ ```
85
+ npm i @graphql-codegen/typescript-compatibility
86
+ ```
87
+
88
+ Run the gqm cli:
89
+
90
+ ```
91
+ npx gqm generate
92
+ ```
93
+
94
+ Start the website:
95
+
96
+ ```
97
+ npm run dev
98
+ ```
99
+
100
+ ### Database setup
101
+
102
+ Adapt the database `.env` variables to connect to a postgresql instance, or create a new one.
103
+ For example, to create a local instance with docker and docker-compose, create the following `docker-compose.yml`:
104
+
105
+ ```
106
+ version: '3.4'
107
+ services:
108
+ postgres:
109
+ image: postgres:13-alpine
110
+ shm_size: 1gb
111
+ environment:
112
+ POSTGRES_DB: postgres
113
+ POSTGRES_USER: postgres
114
+ POSTGRES_PASSWORD: password
115
+ POSTGRES_HOST_AUTH_METHOD: trust
116
+ TZ: 'Europe/Zurich'
117
+ ports:
118
+ - '5432:5432'
119
+ ```
120
+
121
+ Then start it with `docker-compose up`.
122
+
123
+ Generate the first migration:
124
+
125
+ ```
126
+ npx gqm generate-migration
127
+ ```
128
+
129
+ Enter "setup" as migration name. Or you could first create a `feat/setup` git branch, then it would use that name automatically.
130
+
131
+
132
+ Run the migration
133
+
134
+ ```
135
+ npx env-cmd knex migrate:up
136
+ ```
137
+
138
+ ### Auth setup
139
+
140
+ Set up a way for users to authenticate with your app.
141
+ For example, follow [this tutorial](https://auth0.com/docs/quickstart/webapp/nextjs/01-login) to set up auth0.
142
+
143
+ Assuming you used auth0, here's a bare-bones version of what `src/app/page.tsx` could look like:
144
+
145
+ ```
146
+ import { getSession } from '@auth0/nextjs-auth0';
147
+
148
+ export default async function Page() {
149
+ const session = await getSession();
150
+
151
+ return <div>
152
+ <h1>Welcome to my Blog</h1>
153
+ {session ? <a href="/api/auth/logout">Logout</a> : <a href="/api/auth/login">Login</a>}
154
+ </div>
155
+ }
156
+ ```
157
+
158
+ It should now be possible for you to log in and out again.
159
+
160
+ ### Account setup
161
+
162
+ Now, we need to ensure that the user is stored in the database.
163
+
164
+ First extend the user model in `src/config/models.ts` with the following fields:
165
+
166
+ ```
167
+ fields: [
168
+ {
169
+ name: 'authId',
170
+ type: 'String',
171
+ nonNull: true,
172
+ },
173
+ {
174
+ name: 'username',
175
+ type: 'String',
176
+ nonNull: true
177
+ }
178
+ ]
179
+ ```
180
+
181
+ The models have changed, generate the new types:
182
+
183
+ ```
184
+ npx gqm generate
185
+ ```
186
+
187
+ Generate the new migration:
188
+
189
+ ```
190
+ npx gqm generate-migration
191
+ ```
192
+
193
+ Edit the generated migration, then run it
194
+
195
+ ```
196
+ npx env-cmd knex migrate:up
197
+ ```
198
+
199
+ Now let's implement the `// TODO: get user` part in the `src/graphql/execute.ts` file
200
+
201
+ ```
202
+ const session = await getSession();
203
+ if (session) {
204
+ let dbUser = await db('User').where({ authId: session.user.sid }).first();
205
+ if (!user) {
206
+ await db('User').insert({
207
+ id: randomUUID(),
208
+ authId: session.user.sid,
209
+ username: session.user.nickname
210
+ })
211
+ dbUser = await db('User').where({ authId: session.user.sid }).first();
212
+ }
213
+ user = {
214
+ ...dbUser!,
215
+ role: 'ADMIN'
216
+ }
217
+ }
218
+ ```
219
+
220
+ Extend `src/graphql/client/queries/get-me.ts` to also fetch the user's username:
221
+
222
+ ```
223
+ import { gql } from '@smartive/graphql-magic';
224
+
225
+ export const GET_ME = gql`
226
+ query GetMe {
227
+ me {
228
+ id
229
+ username
230
+ }
231
+ }
232
+ `;
233
+ ```
234
+
235
+ Generate the new types:
236
+
237
+ ```
238
+ npx gqm generate
239
+ ```
240
+
241
+ Now, let's modify `src/app/page.tsx` so that it fetches the user from the database:
242
+
243
+ ```
244
+ import { GetMeQuery } from "../generated/client";
245
+ import { GET_ME } from "../graphql/client/queries/get-me";
246
+ import { executeGraphql } from "../graphql/execute";
247
+
248
+ export default async function Page() {
249
+ const { data: { me }} = await executeGraphql<GetMeQuery>({ query: GET_ME });
250
+
251
+ return <div>
252
+ <h1>Welcome to my Blog</h1>
253
+ {me ? <div>Hello {me.username}! <a href="/api/auth/logout">Logout</a></div> : <a href="/api/auth/login">Login</a>}
254
+ </div>
255
+ }
256
+ ```
257
+
258
+ ### Content!
259
+
260
+ Let's create a blog by adding new models in `src/config/models.ts`:
261
+
262
+ ```
263
+ {
264
+ kind: 'entity',
265
+ name: 'Post',
266
+ listQueriable: true,
267
+ creatable: true,
268
+ updatable: true,
269
+ deletable: true,
270
+ fields: [
271
+ {
272
+ name: 'title',
273
+ type: 'String',
274
+ nonNull: true,
275
+ creatable: true,
276
+ updatable: true,
277
+ },
278
+ {
279
+ name: 'content',
280
+ type: 'String',
281
+ nonNull: true,
282
+ creatable: true,
283
+ updatable: true,
284
+ }
285
+ ]
286
+ },
287
+ {
288
+ kind: 'entity',
289
+ name: 'Comment',
290
+ creatable: true,
291
+ updatable: true,
292
+ deletable: true,
293
+ fields: [
294
+ {
295
+ kind: 'relation',
296
+ name: 'post',
297
+ type: 'Post',
298
+ nonNull: true,
299
+ creatable: true,
300
+ },
301
+ {
302
+ name: 'content',
303
+ type: 'String',
304
+ nonNull: true,
305
+ creatable: true,
306
+ updatable: true,
307
+ }
308
+ ]
309
+ ```
310
+
311
+ Generate and run the new migrations and generate the new models:
312
+
313
+ ```
314
+ npx gqm generate-migration
315
+ npx env-cmd knex migrate:up
316
+ npx gqm generate
317
+ ```
318
+
319
+ new get-posts
320
+
321
+ ```
322
+ import { gql } from '@smartive/graphql-magic';
323
+
324
+ export const GET_POSTS = gql`
325
+ query GetPosts {
326
+ posts {
327
+ id
328
+ title
329
+ content
330
+ createdBy {
331
+ username
332
+ }
333
+ comments {
334
+ id
335
+ createdBy {
336
+ username
337
+ }
338
+ content
339
+ }
340
+ }
341
+ }
342
+ `;
343
+ ```
344
+
345
+ ```
346
+ {me && <CreatePost/> }
347
+ <Posts/>
348
+ ```
349
+
350
+ ```
351
+ async function CreatePost() {
352
+ async function createPost(formData: FormData) {
353
+ 'use server'
354
+ await executeGraphql<CreatePostMutationMutation, CreatePostMutationMutationVariables>({
355
+ query: CREATE_POST,
356
+ variables: {
357
+ data: {
358
+ title: formData.get('title') as string,
359
+ content: formData.get('content') as string
360
+ }
361
+ }
362
+ })
363
+ revalidatePath('/')
364
+ }
365
+
366
+ return <form action={createPost}>
367
+ <h2>New Post</h2>
368
+ <div>
369
+ <span>Title</span>
370
+ <input name="title" />
371
+ </div>
372
+ <div>
373
+ <span>Content</span>
374
+ <textarea rows={5} name="content" />
375
+ </div>
376
+ <div>
377
+ <button type="submit">Create</button>
378
+ </div>
379
+ </form>
380
+ }
381
+ ```
382
+
383
+ ```
384
+ async function Posts() {
385
+ const { data: { posts } } = await executeGraphql<GetPostsQuery>({ query: GET_POSTS })
386
+
387
+ return <div>
388
+ {posts.map(post => <div key={post.id}>
389
+ <article>
390
+ <h3>{post.title}</h3>
391
+ <div>{post.createdBy.username}</div>
392
+ <div>{post.content}</div>
393
+ {post.comments.map(comment => (<div key={comment.id}>
394
+ <div>{comment.createdBy.username}</div>
395
+ <p>{comment.content}</p> by {comment.createdBy.username}
396
+ </div>)
397
+ )}
398
+ <CreateComment postId={post.id} />
399
+ </article>
400
+ </div>)}
401
+ </div>
402
+ }
403
+ ```
404
+
405
+ ```
406
+ function CreateComment({ postId }: { postId: string }) {
407
+ async function createComment(formData: FormData) {
408
+ 'use server'
409
+
410
+ const res = await executeGraphql<CreateCommentMutationMutation, CreateCommentMutationMutationVariables>({
411
+ query: CREATE_COMMENT,
412
+ variables: {
413
+ data: {
414
+ postId,
415
+ content: formData.get('content') as string
416
+ }
417
+ }
418
+ })
419
+ console.log(res)
420
+ revalidatePath('/')
421
+ }
422
+ return <form action={createComment}>
423
+ <div>
424
+ <textarea name="content" placeholder="Leave a comment..." />
425
+ </div>
426
+ <div>
427
+ <button type="submit">Send</button>
428
+ </div>
429
+ </form>
430
+ }
431
+ ```