vyriy 0.5.1 → 0.5.3
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/README.md +23 -12
- package/args.js +40 -0
- package/bin/vyriy.js +1 -1
- package/cli.js +67 -0
- package/package.json +51 -6
- package/{cli/types.d.ts → types.d.ts} +7 -1
- package/cli/args.js +0 -29
- package/cli/cli.js +0 -28
- package/commands/check-env.d.ts +0 -2
- package/commands/check-env.js +0 -120
- package/commands/create/index.d.ts +0 -2
- package/commands/create/index.js +0 -135
- package/commands/create/plan/index.d.ts +0 -4
- package/commands/create/plan/index.js +0 -3
- package/commands/create/plan/plan.d.ts +0 -9
- package/commands/create/plan/plan.js +0 -34
- package/commands/create/plan/question.d.ts +0 -2
- package/commands/create/plan/question.js +0 -25
- package/commands/create/plan/types.d.ts +0 -14
- package/commands/create/preset/api.d.ts +0 -2
- package/commands/create/preset/api.js +0 -69
- package/commands/create/preset/base.d.ts +0 -2
- package/commands/create/preset/base.js +0 -195
- package/commands/create/preset/gql.d.ts +0 -2
- package/commands/create/preset/gql.js +0 -750
- package/commands/create/preset/index.d.ts +0 -47
- package/commands/create/preset/index.js +0 -56
- package/commands/create/preset/library.d.ts +0 -2
- package/commands/create/preset/library.js +0 -253
- package/commands/create/preset/mfe.d.ts +0 -2
- package/commands/create/preset/mfe.js +0 -333
- package/commands/create/preset/rest.d.ts +0 -2
- package/commands/create/preset/rest.js +0 -248
- package/commands/create/preset/shared.d.ts +0 -116
- package/commands/create/preset/shared.js +0 -245
- package/commands/create/preset/spa.d.ts +0 -2
- package/commands/create/preset/spa.js +0 -138
- package/commands/create/preset/ssg.d.ts +0 -2
- package/commands/create/preset/ssg.js +0 -177
- package/commands/create/preset/ssr.d.ts +0 -2
- package/commands/create/preset/ssr.js +0 -185
- package/commands/create/preset/types.d.ts +0 -15
- package/commands/create/prompt/conflict-strategy.d.ts +0 -5
- package/commands/create/prompt/conflict-strategy.js +0 -22
- package/commands/create/prompt/index.d.ts +0 -7
- package/commands/create/prompt/index.js +0 -6
- package/commands/create/prompt/preset.d.ts +0 -4
- package/commands/create/prompt/preset.js +0 -11
- package/commands/create/prompt/prompt.d.ts +0 -2
- package/commands/create/prompt/prompt.js +0 -4
- package/commands/create/prompt/provider.d.ts +0 -2
- package/commands/create/prompt/provider.js +0 -13
- package/commands/create/prompt/resolve-option.d.ts +0 -6
- package/commands/create/prompt/resolve-option.js +0 -8
- package/commands/create/prompt/scope.d.ts +0 -2
- package/commands/create/prompt/scope.js +0 -2
- package/commands/create/prompt/types.d.ts +0 -4
- package/commands/dist.d.ts +0 -2
- package/commands/dist.js +0 -287
- package/commands/help.d.ts +0 -3
- package/commands/help.js +0 -24
- package/commands/index.d.ts +0 -5
- package/commands/index.js +0 -5
- package/commands/types.d.ts +0 -44
- package/commands/version.d.ts +0 -2
- package/commands/version.js +0 -6
- /package/{cli/args.d.ts → args.d.ts} +0 -0
- /package/{cli/cli.d.ts → cli.d.ts} +0 -0
- /package/{cli/index.d.ts → index.d.ts} +0 -0
- /package/{cli/index.js → index.js} +0 -0
|
@@ -1,750 +0,0 @@
|
|
|
1
|
-
import packageJson from '../../../package.json' with { type: 'json' };
|
|
2
|
-
import { base } from './base.js';
|
|
3
|
-
export const gql = {
|
|
4
|
-
files: (options) => ({
|
|
5
|
-
...base.files(options),
|
|
6
|
-
'package.json': JSON.stringify({
|
|
7
|
-
name: options.name,
|
|
8
|
-
version: '0.0.0',
|
|
9
|
-
description: options.description,
|
|
10
|
-
private: true,
|
|
11
|
-
type: 'module',
|
|
12
|
-
agents: './AGENTS.md',
|
|
13
|
-
packageManager: packageJson.packageManager,
|
|
14
|
-
engines: {
|
|
15
|
-
node: packageJson.engines.node,
|
|
16
|
-
},
|
|
17
|
-
workspaces: [
|
|
18
|
-
'packages/*',
|
|
19
|
-
'workspaces/*',
|
|
20
|
-
],
|
|
21
|
-
scripts: {
|
|
22
|
-
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
23
|
-
check: 'run-s lint build test',
|
|
24
|
-
fix: "run-s 'fix:*'",
|
|
25
|
-
start: "run-p 'start:*'",
|
|
26
|
-
lint: "run-s 'lint:*'",
|
|
27
|
-
build: "run-s 'build:*'",
|
|
28
|
-
test: "run-s 'test:*'",
|
|
29
|
-
'fix:prettier': 'prettier . --write',
|
|
30
|
-
'fix:eslint': 'eslint . --fix',
|
|
31
|
-
'start:graphql': 'sh workspaces/graphql/bin/start.sh',
|
|
32
|
-
'lint:ts': 'tsc',
|
|
33
|
-
'lint:prettier': 'prettier . --check',
|
|
34
|
-
'lint:eslint': 'eslint .',
|
|
35
|
-
'build:graphql': 'rimraf dist && sh workspaces/graphql/bin/build.sh',
|
|
36
|
-
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
37
|
-
'test:jest': 'jest',
|
|
38
|
-
postinstall: 'husky',
|
|
39
|
-
},
|
|
40
|
-
dependencies: {
|
|
41
|
-
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
42
|
-
typescript: packageJson.peerDependencies.typescript,
|
|
43
|
-
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
44
|
-
prettier: packageJson.peerDependencies.prettier,
|
|
45
|
-
'@vyriy/eslint-config': `^${packageJson.version}`,
|
|
46
|
-
eslint: packageJson.peerDependencies.eslint,
|
|
47
|
-
'@vyriy/jest-config': `^${packageJson.version}`,
|
|
48
|
-
jest: packageJson.peerDependencies.jest,
|
|
49
|
-
'@vyriy/storybook-config': `^${packageJson.version}`,
|
|
50
|
-
storybook: packageJson.peerDependencies.storybook,
|
|
51
|
-
'@vyriy/path': `^${packageJson.version}`,
|
|
52
|
-
husky: packageJson.peerDependencies.husky,
|
|
53
|
-
'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
|
|
54
|
-
'cross-env': packageJson.peerDependencies['cross-env'],
|
|
55
|
-
rimraf: packageJson.peerDependencies.rimraf,
|
|
56
|
-
'@vyriy/webpack-config': `^${packageJson.version}`,
|
|
57
|
-
'@vyriy/handler': `^${packageJson.version}`,
|
|
58
|
-
'@vyriy/server': `^${packageJson.version}`,
|
|
59
|
-
'@vyriy/router': `^${packageJson.version}`,
|
|
60
|
-
tsx: packageJson.peerDependencies.tsx,
|
|
61
|
-
'webpack-cli': packageJson.peerDependencies['webpack-cli'],
|
|
62
|
-
graphql: packageJson.peerDependencies.graphql,
|
|
63
|
-
'@vyriy/html': `^${packageJson.version}`,
|
|
64
|
-
},
|
|
65
|
-
}, null, 2) + '\n',
|
|
66
|
-
'packages/graphql/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
67
|
-
import ReadMe from './README.md?raw';
|
|
68
|
-
|
|
69
|
-
<Meta title="Packages/GraphQL" />
|
|
70
|
-
|
|
71
|
-
<Markdown>{ReadMe}</Markdown>
|
|
72
|
-
`,
|
|
73
|
-
'packages/graphql/graphiql.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
74
|
-
|
|
75
|
-
import { graphiql } from './graphiql.js';
|
|
76
|
-
|
|
77
|
-
describe('packages/graphql/graphiql.ts', () => {
|
|
78
|
-
it('returns the embedded GraphiQL HTML page', () => {
|
|
79
|
-
const page = graphiql();
|
|
80
|
-
|
|
81
|
-
expect(page).toContain('<html lang="en">');
|
|
82
|
-
expect(page).toContain('<title>GraphiQL</title>');
|
|
83
|
-
expect(page).toContain('<meta charset="UTF-8"');
|
|
84
|
-
expect(page).toContain('<div id="graphiql"><div class="loading">Loading');
|
|
85
|
-
expect(page).toContain('https://esm.sh/graphiql@5.2.2/dist/style.css');
|
|
86
|
-
expect(page).toContain('https://esm.sh/@graphiql/plugin-explorer@5.1.1/dist/style.css');
|
|
87
|
-
expect(page).toContain('<script type="importmap">');
|
|
88
|
-
expect(page).toContain('"graphiql": "https://esm.sh/graphiql@5.2.2?standalone');
|
|
89
|
-
expect(page).toContain('const fetcher = createGraphiQLFetcher({');
|
|
90
|
-
expect(page).toContain("url: 'http://localhost:3000'");
|
|
91
|
-
expect(page).toContain('root.render(React.createElement(App));');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
`,
|
|
95
|
-
'packages/graphql/graphiql.ts': `import { html, minify } from '@vyriy/html';
|
|
96
|
-
|
|
97
|
-
export const graphiql = () =>
|
|
98
|
-
minify(
|
|
99
|
-
html({
|
|
100
|
-
htmlAttributes: 'lang="en"',
|
|
101
|
-
title: '<title>GraphiQL</title>',
|
|
102
|
-
meta: '<meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />',
|
|
103
|
-
link: [
|
|
104
|
-
\`<link
|
|
105
|
-
rel="stylesheet"
|
|
106
|
-
href="https://esm.sh/graphiql@5.2.2/dist/style.css"
|
|
107
|
-
integrity="sha384-f6GHLfCwoa4MFYUMd3rieGOsIVAte/evKbJhMigNdzUf52U9bV2JQBMQLke0ua+2"
|
|
108
|
-
crossorigin="anonymous"
|
|
109
|
-
/>\`,
|
|
110
|
-
\`<link
|
|
111
|
-
rel="stylesheet"
|
|
112
|
-
href="https://esm.sh/@graphiql/plugin-explorer@5.1.1/dist/style.css"
|
|
113
|
-
integrity="sha384-vTFGj0krVqwFXLB7kq/VHR0/j2+cCT/B63rge2mULaqnib2OX7DVLUVksTlqvMab"
|
|
114
|
-
crossorigin="anonymous"
|
|
115
|
-
/>\`,
|
|
116
|
-
].join(''),
|
|
117
|
-
style: \`<style>
|
|
118
|
-
body {
|
|
119
|
-
margin: 0;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#graphiql {
|
|
123
|
-
height: 100dvh;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.loading {
|
|
127
|
-
height: 100%;
|
|
128
|
-
display: flex;
|
|
129
|
-
align-items: center;
|
|
130
|
-
justify-content: center;
|
|
131
|
-
font-size: 4rem;
|
|
132
|
-
}
|
|
133
|
-
</style>\`,
|
|
134
|
-
script: [
|
|
135
|
-
\`<script type="importmap">
|
|
136
|
-
{
|
|
137
|
-
"imports": {
|
|
138
|
-
"react": "https://esm.sh/react@19.2.5",
|
|
139
|
-
"react/": "https://esm.sh/react@19.2.5/",
|
|
140
|
-
"react-dom": "https://esm.sh/react-dom@19.2.5",
|
|
141
|
-
"react-dom/": "https://esm.sh/react-dom@19.2.5/",
|
|
142
|
-
"graphiql": "https://esm.sh/graphiql@5.2.2?standalone&external=react,react-dom,@graphiql/react,graphql",
|
|
143
|
-
"graphiql/": "https://esm.sh/graphiql@5.2.2/",
|
|
144
|
-
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer@5.1.1?standalone&external=react,@graphiql/react,graphql",
|
|
145
|
-
"@graphiql/react": "https://esm.sh/@graphiql/react@0.37.3?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
|
|
146
|
-
"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.11.3?standalone&external=graphql",
|
|
147
|
-
"graphql": "https://esm.sh/graphql@16.13.2",
|
|
148
|
-
"@emotion/is-prop-valid": "data:text/javascript,"
|
|
149
|
-
},
|
|
150
|
-
"integrity": {
|
|
151
|
-
"https://esm.sh/react@19.2.5": "sha384-ZNmUQ9QQgyl95nnD/FJTBQn2ZEPTbWtMuWCXTKWNuF6Si7nC+6bvSgk5LWu+ELHn",
|
|
152
|
-
"https://esm.sh/react-dom@19.2.5": "sha384-qtNxBzn9gBs3CmJItMuvIVyjW3VIU0/rzGhCm9MippVU1BpR/c4VgaFYDIg/FrY2",
|
|
153
|
-
"https://esm.sh/graphiql@5.2.2": "sha384-MBVZMq1pmz8DwpwIWPWLk2tmS6tGiSi6WwbXvy9NhuDYASAAWd2m96xbxLqszig9",
|
|
154
|
-
"https://esm.sh/graphiql@5.2.2?standalone&external=react,react-dom,@graphiql/react,graphql": "sha384-SzHBEbcQfhvmwqh5Vtat9k7b/kIzmdVO3KMzQiAYwcxCA9x7vZwFRUgjzN1AeV3q",
|
|
155
|
-
"https://esm.sh/@graphiql/plugin-explorer@5.1.1": "sha384-83REbLb9KtIhL/6J1n91SLoP0648KOKZLIDdHRx/a0E7T3ajq6PzKz+815SCfN52",
|
|
156
|
-
"https://esm.sh/@graphiql/react@0.37.3?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid": "sha384-iZsbTy9B0VcX2BOTdqMuX0uJ9Hff5GbG2QeOt4OeMp0GHza76dwQaYQYNYkZkIVq",
|
|
157
|
-
"https://esm.sh/@graphiql/toolkit@0.11.3?standalone&external=graphql": "sha384-ZsnupyYmzpNjF1Z/81zwi4nV352n4P7vm0JOFKiYnAwVGOf9twnEMnnxmxabMBXe",
|
|
158
|
-
"https://esm.sh/graphql@16.13.2": "sha384-TQg9alwG3P9fzBErDW011vKuyTnrwpBZsl3SdMAh6DwBcv9ezFOl0djGI/68VOyy"
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
</script>\`,
|
|
162
|
-
\`<script type="module">
|
|
163
|
-
import React from 'react';
|
|
164
|
-
import ReactDOM from 'react-dom/client';
|
|
165
|
-
import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
|
|
166
|
-
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
|
167
|
-
import { explorerPlugin } from '@graphiql/plugin-explorer';
|
|
168
|
-
import 'graphiql/setup-workers/esm.sh';
|
|
169
|
-
|
|
170
|
-
const fetcher = createGraphiQLFetcher({
|
|
171
|
-
url: 'http://localhost:3000',
|
|
172
|
-
});
|
|
173
|
-
const plugins = [HISTORY_PLUGIN, explorerPlugin()];
|
|
174
|
-
|
|
175
|
-
function App() {
|
|
176
|
-
return React.createElement(GraphiQL, {
|
|
177
|
-
fetcher,
|
|
178
|
-
plugins,
|
|
179
|
-
defaultEditorToolsVisibility: true,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const container = document.getElementById('graphiql');
|
|
184
|
-
const root = ReactDOM.createRoot(container);
|
|
185
|
-
root.render(React.createElement(App));
|
|
186
|
-
</script>\`,
|
|
187
|
-
].join(''),
|
|
188
|
-
body: '<div id="graphiql"><div class="loading">Loading…</div></div>',
|
|
189
|
-
}),
|
|
190
|
-
);
|
|
191
|
-
`,
|
|
192
|
-
'packages/graphql/index.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
193
|
-
|
|
194
|
-
import { router } from './router.js';
|
|
195
|
-
|
|
196
|
-
describe('packages/graphql/index.ts', () => {
|
|
197
|
-
it('re-exports the GraphQL router', async () => {
|
|
198
|
-
await expect(import('./index.js')).resolves.toMatchObject({
|
|
199
|
-
router,
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
`,
|
|
204
|
-
'packages/graphql/index.ts': "export * from './router.js';\n",
|
|
205
|
-
'packages/graphql/package.json': `{
|
|
206
|
-
"name": "@p/graphql",
|
|
207
|
-
"type": "module",
|
|
208
|
-
"private": true
|
|
209
|
-
}
|
|
210
|
-
`,
|
|
211
|
-
'packages/graphql/README.md': `# @p/graphql
|
|
212
|
-
|
|
213
|
-
Reusable GraphQL API package for the application.
|
|
214
|
-
|
|
215
|
-
## Exports
|
|
216
|
-
|
|
217
|
-
- \`router\` - an \`@vyriy/router\` instance that serves the GraphiQL page and GraphQL HTTP requests.
|
|
218
|
-
|
|
219
|
-
## Routes
|
|
220
|
-
|
|
221
|
-
### \`GET /\`
|
|
222
|
-
|
|
223
|
-
Returns an embedded GraphiQL page for exploring the schema in a browser.
|
|
224
|
-
|
|
225
|
-
### \`POST /\`
|
|
226
|
-
|
|
227
|
-
The router expects a JSON body with a GraphQL request:
|
|
228
|
-
|
|
229
|
-
\`\`\`json
|
|
230
|
-
{
|
|
231
|
-
"query": "{ hello }",
|
|
232
|
-
"variables": {},
|
|
233
|
-
"operationName": "Hello"
|
|
234
|
-
}
|
|
235
|
-
\`\`\`
|
|
236
|
-
|
|
237
|
-
Invalid JSON or an empty body returns \`400\` with \`Invalid JSON body\`. A body without \`query\` returns \`400\` with \`Missing GraphQL query\`.
|
|
238
|
-
|
|
239
|
-
## Schema
|
|
240
|
-
|
|
241
|
-
The package schema currently supports:
|
|
242
|
-
|
|
243
|
-
- \`hello: String\`
|
|
244
|
-
- \`test: Test\`
|
|
245
|
-
- \`ping(message: String): String\`
|
|
246
|
-
|
|
247
|
-
Example mutation:
|
|
248
|
-
|
|
249
|
-
\`\`\`graphql
|
|
250
|
-
mutation Ping($message: String) {
|
|
251
|
-
ping(message: $message)
|
|
252
|
-
}
|
|
253
|
-
\`\`\`
|
|
254
|
-
`,
|
|
255
|
-
'packages/graphql/router.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
256
|
-
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from '@vyriy/router';
|
|
257
|
-
|
|
258
|
-
import { router } from './router.js';
|
|
259
|
-
|
|
260
|
-
type GraphQLBody = {
|
|
261
|
-
data?: Record<string, unknown> | null;
|
|
262
|
-
errors?: Array<{
|
|
263
|
-
message: string;
|
|
264
|
-
}>;
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const getPostEvent = (
|
|
268
|
-
body: string | null,
|
|
269
|
-
headers: APIGatewayProxyEvent['headers'] | null = {
|
|
270
|
-
authorization: 'Bearer token',
|
|
271
|
-
},
|
|
272
|
-
): APIGatewayProxyEvent =>
|
|
273
|
-
({
|
|
274
|
-
body,
|
|
275
|
-
headers,
|
|
276
|
-
httpMethod: 'POST',
|
|
277
|
-
path: '/',
|
|
278
|
-
pathParameters: null,
|
|
279
|
-
queryStringParameters: null,
|
|
280
|
-
}) as unknown as APIGatewayProxyEvent;
|
|
281
|
-
|
|
282
|
-
const getEvent = (path = '/'): APIGatewayProxyEvent =>
|
|
283
|
-
({
|
|
284
|
-
body: null,
|
|
285
|
-
headers: {},
|
|
286
|
-
httpMethod: 'GET',
|
|
287
|
-
path,
|
|
288
|
-
pathParameters: null,
|
|
289
|
-
queryStringParameters: null,
|
|
290
|
-
}) as unknown as APIGatewayProxyEvent;
|
|
291
|
-
|
|
292
|
-
const parseBody = <Value>(response: APIGatewayProxyResult): Value => JSON.parse(response.body) as Value;
|
|
293
|
-
|
|
294
|
-
describe('packages/graphql/router.ts', () => {
|
|
295
|
-
it('serves the embedded GraphiQL page', async () => {
|
|
296
|
-
const response = await router.route(getEvent());
|
|
297
|
-
|
|
298
|
-
expect(response.statusCode).toBe(200);
|
|
299
|
-
expect(response.headers).toEqual({
|
|
300
|
-
'Content-Type': 'text/html; charset=UTF-8',
|
|
301
|
-
});
|
|
302
|
-
expect(response.body).toContain('<title>GraphiQL</title>');
|
|
303
|
-
expect(response.body).toContain('<div id="graphiql">');
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('executes GraphQL requests', async () => {
|
|
307
|
-
const response = await router.route(
|
|
308
|
-
getPostEvent(
|
|
309
|
-
JSON.stringify({
|
|
310
|
-
query: 'query Hello { hello }',
|
|
311
|
-
operationName: 'Hello',
|
|
312
|
-
}),
|
|
313
|
-
),
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
expect(response.statusCode).toBe(200);
|
|
317
|
-
expect(parseBody<GraphQLBody>(response)).toEqual({
|
|
318
|
-
data: {
|
|
319
|
-
hello: 'Hello from GraphQL',
|
|
320
|
-
},
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('passes variables to GraphQL execution', async () => {
|
|
325
|
-
const response = await router.route(
|
|
326
|
-
getPostEvent(
|
|
327
|
-
JSON.stringify({
|
|
328
|
-
query: 'mutation Ping($message: String) { ping(message: $message) }',
|
|
329
|
-
variables: {
|
|
330
|
-
message: 'from router',
|
|
331
|
-
},
|
|
332
|
-
}),
|
|
333
|
-
),
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
expect(response.statusCode).toBe(200);
|
|
337
|
-
expect(parseBody<GraphQLBody>(response)).toEqual({
|
|
338
|
-
data: {
|
|
339
|
-
ping: 'pong: from router',
|
|
340
|
-
},
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('executes GraphQL requests without request headers', async () => {
|
|
345
|
-
const response = await router.route(
|
|
346
|
-
getPostEvent(
|
|
347
|
-
JSON.stringify({
|
|
348
|
-
query: '{ test { ok message } }',
|
|
349
|
-
}),
|
|
350
|
-
null,
|
|
351
|
-
),
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
expect(response.statusCode).toBe(200);
|
|
355
|
-
expect(parseBody<GraphQLBody>(response)).toEqual({
|
|
356
|
-
data: {
|
|
357
|
-
test: {
|
|
358
|
-
ok: true,
|
|
359
|
-
message: 'GraphQL works',
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('rejects missing bodies', async () => {
|
|
366
|
-
await expect(router.route(getPostEvent(null))).resolves.toEqual({
|
|
367
|
-
body: JSON.stringify({
|
|
368
|
-
errors: [{ message: 'Invalid JSON body' }],
|
|
369
|
-
}),
|
|
370
|
-
statusCode: 400,
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('rejects invalid JSON bodies', async () => {
|
|
375
|
-
await expect(router.route(getPostEvent('{'))).resolves.toEqual({
|
|
376
|
-
body: JSON.stringify({
|
|
377
|
-
errors: [{ message: 'Invalid JSON body' }],
|
|
378
|
-
}),
|
|
379
|
-
statusCode: 400,
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('rejects bodies without a GraphQL query', async () => {
|
|
384
|
-
await expect(router.route(getPostEvent(JSON.stringify({ variables: {} })))).resolves.toEqual({
|
|
385
|
-
body: JSON.stringify({
|
|
386
|
-
errors: [{ message: 'Missing GraphQL query' }],
|
|
387
|
-
}),
|
|
388
|
-
statusCode: 400,
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
`,
|
|
393
|
-
'packages/graphql/router.ts': `import { createRouter } from '@vyriy/router';
|
|
394
|
-
|
|
395
|
-
import { graphql } from 'graphql';
|
|
396
|
-
|
|
397
|
-
import { graphiql } from './graphiql.js';
|
|
398
|
-
import { schema } from './schema.js';
|
|
399
|
-
|
|
400
|
-
export const router = createRouter();
|
|
401
|
-
|
|
402
|
-
router.get('/', () => ({
|
|
403
|
-
headers: {
|
|
404
|
-
'Content-Type': 'text/html; charset=UTF-8',
|
|
405
|
-
},
|
|
406
|
-
body: graphiql(),
|
|
407
|
-
}));
|
|
408
|
-
|
|
409
|
-
router.post('/', async (params) => {
|
|
410
|
-
let payload: {
|
|
411
|
-
query?: string;
|
|
412
|
-
variables?: Record<string, unknown>;
|
|
413
|
-
operationName?: string;
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
if (!params.body) {
|
|
417
|
-
return {
|
|
418
|
-
statusCode: 400,
|
|
419
|
-
body: JSON.stringify({
|
|
420
|
-
errors: [{ message: 'Invalid JSON body' }],
|
|
421
|
-
}),
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
try {
|
|
426
|
-
payload = JSON.parse(params.body) as typeof payload;
|
|
427
|
-
} catch {
|
|
428
|
-
return {
|
|
429
|
-
statusCode: 400,
|
|
430
|
-
body: JSON.stringify({
|
|
431
|
-
errors: [{ message: 'Invalid JSON body' }],
|
|
432
|
-
}),
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (!payload.query) {
|
|
437
|
-
return {
|
|
438
|
-
statusCode: 400,
|
|
439
|
-
body: JSON.stringify({
|
|
440
|
-
errors: [{ message: 'Missing GraphQL query' }],
|
|
441
|
-
}),
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
return {
|
|
446
|
-
body: JSON.stringify(
|
|
447
|
-
await graphql({
|
|
448
|
-
schema,
|
|
449
|
-
source: payload.query,
|
|
450
|
-
variableValues: payload.variables,
|
|
451
|
-
operationName: payload.operationName,
|
|
452
|
-
contextValue: {
|
|
453
|
-
event: params.event,
|
|
454
|
-
headers: params.headers ?? {},
|
|
455
|
-
},
|
|
456
|
-
}),
|
|
457
|
-
),
|
|
458
|
-
};
|
|
459
|
-
});
|
|
460
|
-
`,
|
|
461
|
-
'packages/graphql/schema.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
462
|
-
import { graphql } from 'graphql';
|
|
463
|
-
|
|
464
|
-
import { schema } from './schema.js';
|
|
465
|
-
|
|
466
|
-
describe('packages/graphql/schema.ts', () => {
|
|
467
|
-
it('resolves query fields', async () => {
|
|
468
|
-
await expect(
|
|
469
|
-
graphql({
|
|
470
|
-
schema,
|
|
471
|
-
source: '{ hello test { ok message } }',
|
|
472
|
-
}),
|
|
473
|
-
).resolves.toEqual({
|
|
474
|
-
data: {
|
|
475
|
-
hello: 'Hello from GraphQL',
|
|
476
|
-
test: {
|
|
477
|
-
ok: true,
|
|
478
|
-
message: 'GraphQL works',
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
});
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
it('resolves the ping mutation', async () => {
|
|
485
|
-
await expect(
|
|
486
|
-
graphql({
|
|
487
|
-
schema,
|
|
488
|
-
source: 'mutation Ping($message: String) { ping(message: $message) }',
|
|
489
|
-
variableValues: {
|
|
490
|
-
message: 'hello',
|
|
491
|
-
},
|
|
492
|
-
}),
|
|
493
|
-
).resolves.toEqual({
|
|
494
|
-
data: {
|
|
495
|
-
ping: 'pong: hello',
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
});
|
|
500
|
-
`,
|
|
501
|
-
'packages/graphql/schema.ts': `import { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLBoolean } from 'graphql';
|
|
502
|
-
|
|
503
|
-
const TestType = new GraphQLObjectType({
|
|
504
|
-
name: 'Test',
|
|
505
|
-
fields: {
|
|
506
|
-
ok: {
|
|
507
|
-
type: GraphQLBoolean,
|
|
508
|
-
},
|
|
509
|
-
message: {
|
|
510
|
-
type: GraphQLString,
|
|
511
|
-
},
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
const QueryType = new GraphQLObjectType({
|
|
516
|
-
name: 'Query',
|
|
517
|
-
fields: {
|
|
518
|
-
hello: {
|
|
519
|
-
type: GraphQLString,
|
|
520
|
-
resolve: () => 'Hello from GraphQL',
|
|
521
|
-
},
|
|
522
|
-
|
|
523
|
-
test: {
|
|
524
|
-
type: TestType,
|
|
525
|
-
resolve: () => ({
|
|
526
|
-
ok: true,
|
|
527
|
-
message: 'GraphQL works',
|
|
528
|
-
}),
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
const MutationType = new GraphQLObjectType({
|
|
534
|
-
name: 'Mutation',
|
|
535
|
-
fields: {
|
|
536
|
-
ping: {
|
|
537
|
-
type: GraphQLString,
|
|
538
|
-
args: {
|
|
539
|
-
message: {
|
|
540
|
-
type: GraphQLString,
|
|
541
|
-
},
|
|
542
|
-
},
|
|
543
|
-
resolve: (_source, args: { message: string }) => {
|
|
544
|
-
return \`pong: \${args.message}\`;
|
|
545
|
-
},
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
export const schema = new GraphQLSchema({
|
|
551
|
-
query: QueryType,
|
|
552
|
-
mutation: MutationType,
|
|
553
|
-
});
|
|
554
|
-
`,
|
|
555
|
-
'workspaces/graphql/bin/build.sh': `#!/usr/bin/env sh
|
|
556
|
-
|
|
557
|
-
set -e
|
|
558
|
-
|
|
559
|
-
scriptdir="$PWD/workspaces/graphql";
|
|
560
|
-
distdir="dist/graphql";
|
|
561
|
-
|
|
562
|
-
NODE_ENV=production npx webpack --config $scriptdir/webpack.config.ts
|
|
563
|
-
|
|
564
|
-
cp $scriptdir/package.json $distdir/package.json
|
|
565
|
-
npm pkg delete "type" --prefix $distdir
|
|
566
|
-
npm pkg delete "private" --prefix $distdir
|
|
567
|
-
`,
|
|
568
|
-
'workspaces/graphql/bin/start.sh': `#!/usr/bin/env sh
|
|
569
|
-
|
|
570
|
-
set -e
|
|
571
|
-
|
|
572
|
-
scriptdir="$PWD/workspaces/graphql";
|
|
573
|
-
|
|
574
|
-
NODE_ENV=production LOG_LEVEL=info tsx $scriptdir/index.ts
|
|
575
|
-
`,
|
|
576
|
-
'workspaces/graphql/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
577
|
-
import ReadMe from './README.md?raw';
|
|
578
|
-
|
|
579
|
-
<Meta title="Workspaces/Graphql" />
|
|
580
|
-
|
|
581
|
-
<Markdown>{ReadMe}</Markdown>
|
|
582
|
-
`,
|
|
583
|
-
'workspaces/graphql/index.test.ts': `import { describe, expect, it, jest } from '@jest/globals';
|
|
584
|
-
import type { APIGatewayProxyEvent } from '@vyriy/router';
|
|
585
|
-
|
|
586
|
-
const apiMock = jest.fn((handler) => ({
|
|
587
|
-
handler,
|
|
588
|
-
}));
|
|
589
|
-
const serverMock = jest.fn();
|
|
590
|
-
|
|
591
|
-
jest.mock('@vyriy/handler', () => ({
|
|
592
|
-
api: apiMock,
|
|
593
|
-
}));
|
|
594
|
-
|
|
595
|
-
jest.mock('@vyriy/server', () => ({
|
|
596
|
-
server: serverMock,
|
|
597
|
-
}));
|
|
598
|
-
|
|
599
|
-
describe('workspaces/graphql/index.ts', () => {
|
|
600
|
-
const getPostEvent = (body: string | null, path = '/'): APIGatewayProxyEvent =>
|
|
601
|
-
({
|
|
602
|
-
body,
|
|
603
|
-
headers: {},
|
|
604
|
-
httpMethod: 'POST',
|
|
605
|
-
path,
|
|
606
|
-
pathParameters: null,
|
|
607
|
-
queryStringParameters: null,
|
|
608
|
-
}) as unknown as APIGatewayProxyEvent;
|
|
609
|
-
|
|
610
|
-
const getEvent = (path = '/'): APIGatewayProxyEvent =>
|
|
611
|
-
({
|
|
612
|
-
body: null,
|
|
613
|
-
headers: {},
|
|
614
|
-
httpMethod: 'GET',
|
|
615
|
-
path,
|
|
616
|
-
pathParameters: null,
|
|
617
|
-
queryStringParameters: null,
|
|
618
|
-
}) as unknown as APIGatewayProxyEvent;
|
|
619
|
-
|
|
620
|
-
it('starts the server with the API router handler', async () => {
|
|
621
|
-
await import('./index.js');
|
|
622
|
-
|
|
623
|
-
expect(apiMock).toHaveBeenCalledTimes(1);
|
|
624
|
-
expect(serverMock).toHaveBeenCalledTimes(1);
|
|
625
|
-
expect(serverMock).toHaveBeenCalledWith(apiMock.mock.results[0]?.value);
|
|
626
|
-
|
|
627
|
-
const handler = apiMock.mock.calls[0]?.[0] as (event: APIGatewayProxyEvent) => Promise<{
|
|
628
|
-
body: string;
|
|
629
|
-
headers?: Record<string, string>;
|
|
630
|
-
statusCode: number;
|
|
631
|
-
}>;
|
|
632
|
-
|
|
633
|
-
await expect(
|
|
634
|
-
handler(
|
|
635
|
-
getPostEvent(
|
|
636
|
-
JSON.stringify({
|
|
637
|
-
query: '{ hello test { ok message } }',
|
|
638
|
-
}),
|
|
639
|
-
),
|
|
640
|
-
),
|
|
641
|
-
).resolves.toEqual({
|
|
642
|
-
body: JSON.stringify({
|
|
643
|
-
data: {
|
|
644
|
-
hello: 'Hello from GraphQL',
|
|
645
|
-
test: {
|
|
646
|
-
ok: true,
|
|
647
|
-
message: 'GraphQL works',
|
|
648
|
-
},
|
|
649
|
-
},
|
|
650
|
-
}),
|
|
651
|
-
statusCode: 200,
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
const graphiqlResponse = await handler(getEvent());
|
|
655
|
-
|
|
656
|
-
expect(graphiqlResponse).toEqual({
|
|
657
|
-
body: expect.stringContaining('<title>GraphiQL</title>'),
|
|
658
|
-
headers: {
|
|
659
|
-
'Content-Type': 'text/html; charset=UTF-8',
|
|
660
|
-
},
|
|
661
|
-
statusCode: 200,
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
await expect(handler(getEvent('/healthcheck'))).resolves.toEqual({
|
|
665
|
-
body: JSON.stringify({
|
|
666
|
-
message: 'Not Found',
|
|
667
|
-
}),
|
|
668
|
-
statusCode: 404,
|
|
669
|
-
});
|
|
670
|
-
});
|
|
671
|
-
});
|
|
672
|
-
`,
|
|
673
|
-
'workspaces/graphql/index.ts': `import { server } from '@vyriy/server';
|
|
674
|
-
import { api } from '@vyriy/handler';
|
|
675
|
-
|
|
676
|
-
import { router } from '@p/graphql';
|
|
677
|
-
|
|
678
|
-
server(api(async (event) => router.route(event)));
|
|
679
|
-
`,
|
|
680
|
-
'workspaces/graphql/package.json': `{
|
|
681
|
-
"name": "@w/graphql",
|
|
682
|
-
"type": "module",
|
|
683
|
-
"private": true
|
|
684
|
-
}
|
|
685
|
-
`,
|
|
686
|
-
'workspaces/graphql/README.md': `# @w/graphql
|
|
687
|
-
|
|
688
|
-
GraphQL runtime workspace for the application.
|
|
689
|
-
|
|
690
|
-
This workspace starts the HTTP server and mounts the reusable \`@p/graphql\` router through \`@vyriy/handler\` and \`@vyriy/server\`.
|
|
691
|
-
|
|
692
|
-
## Routes
|
|
693
|
-
|
|
694
|
-
- \`GET /\` - serves the embedded GraphiQL page.
|
|
695
|
-
- \`POST /\` - executes GraphQL requests against the package schema.
|
|
696
|
-
|
|
697
|
-
Example request body:
|
|
698
|
-
|
|
699
|
-
\`\`\`json
|
|
700
|
-
{
|
|
701
|
-
"query": "{ hello test { ok message } }"
|
|
702
|
-
}
|
|
703
|
-
\`\`\`
|
|
704
|
-
|
|
705
|
-
## Development
|
|
706
|
-
|
|
707
|
-
Start the local GraphQL server:
|
|
708
|
-
|
|
709
|
-
\`\`\`bash
|
|
710
|
-
yarn start:graphql
|
|
711
|
-
\`\`\`
|
|
712
|
-
|
|
713
|
-
Build the deployable bundle:
|
|
714
|
-
|
|
715
|
-
\`\`\`bash
|
|
716
|
-
yarn build:graphql
|
|
717
|
-
\`\`\`
|
|
718
|
-
|
|
719
|
-
The build output is written to \`dist/graphql\`.
|
|
720
|
-
|
|
721
|
-
## Implementation
|
|
722
|
-
|
|
723
|
-
The workspace entrypoint is \`index.ts\`. It wraps the shared router with \`api()\` and passes it to \`server()\`:
|
|
724
|
-
|
|
725
|
-
\`\`\`ts
|
|
726
|
-
server(api(async (event) => router.route(event)));
|
|
727
|
-
\`\`\`
|
|
728
|
-
`,
|
|
729
|
-
'workspaces/graphql/webpack.config.ts': `import { path } from '@vyriy/path';
|
|
730
|
-
import { ssr, external } from '@vyriy/webpack-config';
|
|
731
|
-
|
|
732
|
-
export default ssr(
|
|
733
|
-
'@w/graphql',
|
|
734
|
-
{
|
|
735
|
-
path: path('dist', 'graphql'),
|
|
736
|
-
filename: 'index.js',
|
|
737
|
-
library: { type: 'commonjs2' },
|
|
738
|
-
},
|
|
739
|
-
(config) => ({
|
|
740
|
-
...config,
|
|
741
|
-
externals: [external({ allowlist: [/^@p/, /^@w/, /^@vyriy/] })],
|
|
742
|
-
}),
|
|
743
|
-
);
|
|
744
|
-
`,
|
|
745
|
-
}),
|
|
746
|
-
ci: {
|
|
747
|
-
...base.ci,
|
|
748
|
-
},
|
|
749
|
-
deploy: {},
|
|
750
|
-
};
|