From 8f702919e9a496a3de8cb22e035d4525a354a5d1 Mon Sep 17 00:00:00 2001 From: Alec Aivazis Date: Mon, 19 Dec 2022 13:15:24 -0800 Subject: [PATCH] Graphql as function (#776) * find_graphql looks for graphql function * dry up js and svelte processing * extract load functions * reactive transform respect graphql functions * cleanup * add graphql_tag_export to plugins * compute overloaded return values * generate graphql overloads * remove unused import and types * fix extraction * tests pass * support root graphql functions too * unused import * changeset * generate typedefs for fragment functions * linter * linter and update snapshots * update snapshots * update all docs to use graphql function * remove unnecessary type import * add graphql function to release notes * doc tweak * missed one * clean up comments * can -> should * docs housekeeping * remove file * include paginated fragments as valid args to fragment --- .changeset/dull-steaks-grab.md | 6 + README.md | 4 +- e2e/sveltekit/src/lib/QueryComponent.svelte | 4 +- .../src/lib/QueryExtNoAutoFetch.svelte | 4 +- e2e/sveltekit/src/routes/+layout.svelte | 4 +- .../routes/isFetching/route_1/+page.svelte | 4 +- .../routes/isFetching/with_load/+page.svelte | 4 +- .../fragment/backwards-cursor/+page.svelte | 23 +- .../fragment/forward-cursor/+page.svelte | 23 +- .../pagination/fragment/offset/+page.svelte | 21 +- .../query/backwards-cursor/+page.svelte | 6 +- .../src/routes/query-param/UserName.svelte | 4 +- packages/houdini-react/src/plugin/extract.ts | 3 +- .../houdini-react/src/plugin/transform.ts | 3 - .../fragmentTypedefs/fragmentTypedefs.test.ts | 89 ++++++++ .../plugin/codegen/fragmentTypedefs/index.ts | 153 ++++++++++++++ .../src/plugin/codegen/index.ts | 2 + packages/houdini-svelte/src/plugin/extract.ts | 28 +-- .../src/plugin/extractLoadFunction.test.ts | 52 +++++ .../src/plugin/extractLoadFunction.ts | 41 ++++ packages/houdini-svelte/src/plugin/index.ts | 27 ++- .../src/plugin/transforms/kit/load.test.ts | 100 ++++++++- .../src/plugin/transforms/query.test.ts | 62 ++++++ .../src/plugin/transforms/reactive.test.ts | 69 ++++++ .../src/plugin/transforms/reactive.ts | 45 ++-- .../src/plugin/transforms/tags.ts | 2 +- .../generators/indexFile/indexFile.test.ts | 1 + .../src/codegen/generators/runtime/index.ts | 92 +++++++- packages/houdini/src/codegen/index.ts | 9 +- packages/houdini/src/lib/config.ts | 11 +- packages/houdini/src/lib/fs.ts | 3 +- packages/houdini/src/lib/walk.ts | 134 +++++++----- packages/houdini/src/runtime/index.ts | 6 +- pnpm-lock.yaml | 200 +++++++++++++++++- site/src/routes/+page.svelte | 28 +-- site/src/routes/api/fragments/+page.svx | 8 +- site/src/routes/api/mutation/+page.svx | 25 ++- site/src/routes/api/query/+page.svx | 40 ++-- site/src/routes/api/subscription/+page.svx | 6 +- site/src/routes/api/welcome/+page.svx | 31 ++- site/src/routes/guides/faq/+page.svx | 6 +- site/src/routes/guides/pagination/+page.svx | 20 +- .../src/routes/guides/release-notes/+page.svx | 39 +++- site/src/routes/guides/typescript/+page.svx | 28 ++- .../routes/guides/uploading-files/+page.svx | 4 +- .../guides/working-with-graphql/+page.svx | 54 ++--- site/src/routes/intro/fetching-data/+page.svx | 8 +- site/src/routes/intro/fragments/+page.svx | 24 ++- site/src/routes/intro/mutations/+page.svx | 8 +- site/src/routes/intro/pagination/+page.svx | 4 +- 50 files changed, 1257 insertions(+), 315 deletions(-) create mode 100644 .changeset/dull-steaks-grab.md create mode 100644 packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/fragmentTypedefs.test.ts create mode 100644 packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/index.ts create mode 100644 packages/houdini-svelte/src/plugin/transforms/reactive.test.ts diff --git a/.changeset/dull-steaks-grab.md b/.changeset/dull-steaks-grab.md new file mode 100644 index 0000000000..ce7a85fdf9 --- /dev/null +++ b/.changeset/dull-steaks-grab.md @@ -0,0 +1,6 @@ +--- +'houdini': patch +'houdini-svelte': patch +--- + +graphql template tag can now be used as a function for automatic typing diff --git a/README.md b/README.md index 80e1c80a9b..6b13336d32 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,13 @@ // src/routes/items/+page.svelte import { graphql } from '$houdini' - const AllItems = graphql` + const AllItems = graphql(` query AllItems { items { text } } - ` + `) {#each $AllItems.data.items as item} diff --git a/e2e/sveltekit/src/lib/QueryComponent.svelte b/e2e/sveltekit/src/lib/QueryComponent.svelte index e7fd2f886a..69ffd407fc 100644 --- a/e2e/sveltekit/src/lib/QueryComponent.svelte +++ b/e2e/sveltekit/src/lib/QueryComponent.svelte @@ -10,13 +10,13 @@ // svelte-ignore unused-export-let export let id = ''; - const result: FragmentQueryVarsStore = graphql` + const result = graphql(` query FragmentQueryVars($id: ID!) { user(id: $id, snapshot: "preprocess-query-variable") { name } } - `; + `); {$result.data?.user.name} diff --git a/e2e/sveltekit/src/lib/QueryExtNoAutoFetch.svelte b/e2e/sveltekit/src/lib/QueryExtNoAutoFetch.svelte index 7965f98f26..81f48875ec 100644 --- a/e2e/sveltekit/src/lib/QueryExtNoAutoFetch.svelte +++ b/e2e/sveltekit/src/lib/QueryExtNoAutoFetch.svelte @@ -1,14 +1,14 @@

diff --git a/e2e/sveltekit/src/routes/+layout.svelte b/e2e/sveltekit/src/routes/+layout.svelte index 415d0ea2fe..636488d608 100644 --- a/e2e/sveltekit/src/routes/+layout.svelte +++ b/e2e/sveltekit/src/routes/+layout.svelte @@ -17,11 +17,11 @@ return { key, value: (routes as Record)[key] }; }); - const info = graphql` + const info = graphql(` query LayoutSession { session } - `; + `); diff --git a/e2e/sveltekit/src/routes/isFetching/route_1/+page.svelte b/e2e/sveltekit/src/routes/isFetching/route_1/+page.svelte index 70b883696a..55e3e6a4d6 100644 --- a/e2e/sveltekit/src/routes/isFetching/route_1/+page.svelte +++ b/e2e/sveltekit/src/routes/isFetching/route_1/+page.svelte @@ -1,14 +1,14 @@ route_2 diff --git a/e2e/sveltekit/src/routes/isFetching/with_load/+page.svelte b/e2e/sveltekit/src/routes/isFetching/with_load/+page.svelte index 825966a7a7..5f5dabd329 100644 --- a/e2e/sveltekit/src/routes/isFetching/with_load/+page.svelte +++ b/e2e/sveltekit/src/routes/isFetching/with_load/+page.svelte @@ -1,14 +1,14 @@ diff --git a/e2e/sveltekit/src/routes/pagination/fragment/backwards-cursor/+page.svelte b/e2e/sveltekit/src/routes/pagination/fragment/backwards-cursor/+page.svelte index fc73bdf9fa..d79bb5ccf3 100644 --- a/e2e/sveltekit/src/routes/pagination/fragment/backwards-cursor/+page.svelte +++ b/e2e/sveltekit/src/routes/pagination/fragment/backwards-cursor/+page.svelte @@ -1,22 +1,17 @@

- {$fragmentResult.data?.friendsConnection.edges.map(({ node }) => node?.name).join(', ')} + {$fragmentResult?.data?.friendsConnection.edges.map(({ node }) => node?.name).join(', ')}
- {JSON.stringify($fragmentResult.pageInfo)} + {JSON.stringify($fragmentResult?.pageInfo)}
- + diff --git a/e2e/sveltekit/src/routes/pagination/fragment/forward-cursor/+page.svelte b/e2e/sveltekit/src/routes/pagination/fragment/forward-cursor/+page.svelte index 37a7e95e25..02da5641c9 100644 --- a/e2e/sveltekit/src/routes/pagination/fragment/forward-cursor/+page.svelte +++ b/e2e/sveltekit/src/routes/pagination/fragment/forward-cursor/+page.svelte @@ -1,22 +1,17 @@
- {$fragmentResult.data?.friendsConnection.edges.map(({ node }) => node?.name).join(', ')} + {$fragmentResult?.data?.friendsConnection.edges.map(({ node }) => node?.name).join(', ')}
- {JSON.stringify($fragmentResult.pageInfo)} + {JSON.stringify($fragmentResult?.pageInfo)}
- + diff --git a/e2e/sveltekit/src/routes/pagination/fragment/offset/+page.svelte b/e2e/sveltekit/src/routes/pagination/fragment/offset/+page.svelte index fc99d0f354..f9aa76b717 100644 --- a/e2e/sveltekit/src/routes/pagination/fragment/offset/+page.svelte +++ b/e2e/sveltekit/src/routes/pagination/fragment/offset/+page.svelte @@ -1,33 +1,28 @@
- {$fragmentResult.data?.friendsList.map((node) => node?.name).join(', ')} + {$fragmentResult?.data?.friendsList.map((node) => node?.name).join(', ')}
- + diff --git a/e2e/sveltekit/src/routes/pagination/query/backwards-cursor/+page.svelte b/e2e/sveltekit/src/routes/pagination/query/backwards-cursor/+page.svelte index 89f85e292e..a3c1c229d8 100644 --- a/e2e/sveltekit/src/routes/pagination/query/backwards-cursor/+page.svelte +++ b/e2e/sveltekit/src/routes/pagination/query/backwards-cursor/+page.svelte @@ -1,7 +1,7 @@
diff --git a/e2e/sveltekit/src/routes/query-param/UserName.svelte b/e2e/sveltekit/src/routes/query-param/UserName.svelte index c88a6429cf..bccf79c6fa 100644 --- a/e2e/sveltekit/src/routes/query-param/UserName.svelte +++ b/e2e/sveltekit/src/routes/query-param/UserName.svelte @@ -10,11 +10,11 @@ user && fragment( user, - graphql` + graphql(/* GraphQL */ ` fragment UserName on User { name } - ` + `) ); diff --git a/packages/houdini-react/src/plugin/extract.ts b/packages/houdini-react/src/plugin/extract.ts index 932a5970a7..35ee902eea 100644 --- a/packages/houdini-react/src/plugin/extract.ts +++ b/packages/houdini-react/src/plugin/extract.ts @@ -1,7 +1,8 @@ import { parse } from '@babel/parser' +import { Config } from 'houdini' import * as recast from 'recast' -export function extract_documents(filepath: string, content: string) { +export function extract_documents(config: Config, filepath: string, content: string) { // the documents we've found const documents: string[] = [] diff --git a/packages/houdini-react/src/plugin/transform.ts b/packages/houdini-react/src/plugin/transform.ts index 70e993aa9a..0532600b67 100644 --- a/packages/houdini-react/src/plugin/transform.ts +++ b/packages/houdini-react/src/plugin/transform.ts @@ -34,13 +34,10 @@ export function transform_file(page: TransformPage): { code: string } { query = argument.quasis[0].value.raw } else if (argument.type === 'StringLiteral') { query = argument.value - } else { - console.log(value.callee.name) } // we want to replace the template tag with an import to the appropriate // artifact - let name = page.config.documentName(graphql.parse(query)) let artifact_name = ensureArtifactImport({ config: page.config, diff --git a/packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/fragmentTypedefs.test.ts b/packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/fragmentTypedefs.test.ts new file mode 100644 index 0000000000..be6fedcb13 --- /dev/null +++ b/packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/fragmentTypedefs.test.ts @@ -0,0 +1,89 @@ +import { parseJS, fs, path } from 'houdini' +import { mockCollectedDoc, testConfig } from 'houdini/test' +import { test, expect } from 'vitest' + +import generate from '..' + +const config = testConfig() +const plugin_root = config.pluginDirectory('hodini-svelte') + +test('generates types for fragments', async function () { + // create the mock filesystem + await fs.mock({ + [path.join(config.pluginDirectory('houdini-svelte'), 'runtime', 'fragments.d.ts')]: ` + import { Fragment } from '$houdini/runtime/lib/types'; + import { Readable } from 'svelte/store'; + import { FragmentStore } from './stores'; + import type { FragmentStorePaginated } from './stores/pagination/fragment'; + + export declare function fragment<_Fragment extends Fragment>(ref: _Fragment, fragment: FragmentStore<_Fragment['shape']>): Readable> & { + data: Readable<_Fragment>; + }; + export declare function fragment<_Fragment extends Fragment>(ref: _Fragment | null, fragment: FragmentStore<_Fragment['shape']>): Readable | null> & { + data: Readable<_Fragment | null>; + }; + export declare function paginatedFragment<_Fragment extends Fragment>(initialValue: _Fragment | null, document: FragmentStore<_Fragment['shape']>): FragmentStorePaginated<_Fragment['shape'], {}>; + export declare function paginatedFragment<_Fragment extends Fragment>(initialValue: _Fragment, document: FragmentStore<_Fragment['shape']>): FragmentStorePaginated<_Fragment['shape'], {}>; + `, + }) + + // execute the generator + await generate({ + config, + documents: [mockCollectedDoc(`fragment TestFragment on Query { viewer { id } } `)], + framework: 'kit', + plugin_root, + }) + + // load the contents of the file + const queryContents = await fs.readFile( + path.join(config.pluginRuntimeDirectory('houdini-svelte'), 'fragments.d.ts') + ) + + expect(queryContents).toBeTruthy() + + //the parser doesn't work right but the type imports are correct. + const parsedQuery = (await parseJS(queryContents!))?.script + + // verify contents + expect(parsedQuery).toMatchInlineSnapshot(` + import { TestFragmentStore } from "../stores/TestFragment"; + import { Fragment } from "$houdini/runtime/lib/types"; + import { Readable } from "svelte/store"; + import { FragmentStore } from "./stores"; + import type { FragmentStorePaginated } from "./stores/pagination/fragment"; + + export function fragment( + initialValue: { + $fragments: { + TestFragment: true; + }; + }, + document: TestFragmentStore + ): ReturnType; + + export function fragment( + initialValue: { + $fragments: { + TestFragment: true; + }; + } | null, + document: TestFragmentStore + ): ReturnType | null; + + export declare function fragment<_Fragment extends Fragment>(ref: _Fragment, fragment: FragmentStore<_Fragment["shape"]>): Readable> & { + data: Readable<_Fragment>; + }; + + export declare function fragment<_Fragment extends Fragment>(ref: _Fragment | null, fragment: FragmentStore<_Fragment["shape"]>): Readable | null> & { + data: Readable<_Fragment | null>; + }; + + export declare function paginatedFragment<_Fragment extends Fragment>( + initialValue: _Fragment | null, + document: FragmentStore<_Fragment["shape"]> + ): FragmentStorePaginated<_Fragment["shape"], {}>; + + export declare function paginatedFragment<_Fragment extends Fragment>(initialValue: _Fragment, document: FragmentStore<_Fragment["shape"]>): FragmentStorePaginated<_Fragment["shape"], {}>; + `) +}) diff --git a/packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/index.ts b/packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/index.ts new file mode 100644 index 0000000000..70cd22b6e0 --- /dev/null +++ b/packages/houdini-svelte/src/plugin/codegen/fragmentTypedefs/index.ts @@ -0,0 +1,153 @@ +import { StatementKind } from 'ast-types/lib/gen/kinds' +import { parseJS, path, fs, ArtifactKind, ensureImports, CollectedGraphQLDocument } from 'houdini' +import * as recast from 'recast' + +import { PluginGenerateInput } from '..' +import { stores_directory_name, store_name } from '../../kit' + +const AST = recast.types.builders + +export default async function fragmentTypedefs(input: PluginGenerateInput) { + // before we update the typedefs lets find all of the fragments so we can overload the correct function + let fragments: Record> = {} + + for (const doc of input.documents) { + if (doc.kind === ArtifactKind.Fragment) { + // if the fragment is paginated, add it to the paginated one + if (doc.refetch?.paginated) { + fragments = { + ...fragments, + ['paginatedFragment']: { + ...fragments['paginatedFragment'], + [doc.originalString]: doc, + }, + } + } + + // always add the fragment + fragments = { + ...fragments, + ['fragment']: { + ...fragments['fragment'], + [doc.originalString]: doc, + }, + } + } + } + + // find the path for the fragment typedefs + const target_path = path.join( + input.config.pluginRuntimeDirectory('houdini-svelte'), + 'fragments.d.ts' + ) + + const contents = await parseJS((await fs.readFile(target_path)) || '')! + if (!contents) { + return + } + + function insert_exports(which: string, statements: StatementKind[]) { + for (const [i, expression] of [...(contents!.script.body ?? [])].entries()) { + if ( + expression.type !== 'ExportNamedDeclaration' || + expression.declaration?.type !== 'TSDeclareFunction' || + expression.declaration.id?.name !== which + ) { + continue + } + + // it should return the right thing + contents!.script.body.splice(i, 0, ...statements) + + // we're done + break + } + } + + for (const [which, docs] of Object.entries(fragments)) { + // insert a definition for every fragment + insert_exports( + which, + Object.entries(docs).flatMap(([queryString, doc]) => { + if (!doc.generateStore) { + return [] + } + + // make sure we are importing the store + const store = store_name({ config: input.config, name: doc.name }) + const import_path = path.join('..', stores_directory_name(), doc.name) + // build up the documentInput with the query string as a hard coded value + const fragment_map = AST.tsTypeLiteral([ + AST.tsPropertySignature( + AST.identifier('$fragments'), + AST.tsTypeAnnotation( + AST.tsTypeLiteral([ + AST.tsPropertySignature( + AST.identifier(doc.name), + AST.tsTypeAnnotation( + AST.tsLiteralType(AST.booleanLiteral(true)) + ) + ), + ]) + ) + ), + ]) + // build up the 2 input options + const initial_value_input = AST.identifier('initialValue') + initial_value_input.typeAnnotation = AST.tsTypeAnnotation(fragment_map) + const initial_value_or_null_input = AST.identifier('initialValue') + initial_value_or_null_input.typeAnnotation = AST.tsTypeAnnotation( + AST.tsUnionType([fragment_map, AST.tsNullKeyword()]) + ) + + // regardless of the input value, we need to pass the document store + const document_input = AST.identifier('document') + document_input.typeAnnotation = AST.tsTypeAnnotation( + AST.tsTypeReference(AST.identifier(store)) + ) + + // the return value + const return_value = AST.tsTypeReference( + AST.identifier('ReturnType'), + AST.tsTypeParameterInstantiation([ + AST.tsIndexedAccessType( + AST.tsTypeReference(AST.identifier(store)), + AST.tsLiteralType(AST.stringLiteral('get')) + ), + ]) + ) + + // make sure the store is imported + ensureImports({ + config: input.config, + body: contents!.script.body!, + sourceModule: import_path, + import: [store], + }) + + // if the user passes the string, return the correct store + return [ + AST.exportNamedDeclaration( + AST.tsDeclareFunction( + AST.identifier(which), + [initial_value_input, document_input], + AST.tsTypeAnnotation(return_value) + ) + ), + AST.exportNamedDeclaration( + AST.tsDeclareFunction( + AST.identifier(which), + [initial_value_or_null_input, document_input], + AST.tsTypeAnnotation( + AST.tsUnionType([return_value, AST.tsNullKeyword()]) + ) + ) + ), + ] + }) + ) + } + + // write the updated file + await fs.writeFile(target_path, recast.prettyPrint(contents.script).code) +} diff --git a/packages/houdini-svelte/src/plugin/codegen/index.ts b/packages/houdini-svelte/src/plugin/codegen/index.ts index ddb8efff98..a46bb1d8a0 100644 --- a/packages/houdini-svelte/src/plugin/codegen/index.ts +++ b/packages/houdini-svelte/src/plugin/codegen/index.ts @@ -2,6 +2,7 @@ import { GenerateHookInput, fs, Config } from 'houdini' import { stores_directory, type_route_dir } from '../kit' import components from './components' +import fragmentTypedefs from './fragmentTypedefs' import kit from './routes' import stores from './stores' @@ -17,6 +18,7 @@ export default async function (input: PluginGenerateInput) { kit(input.framework, input), stores(input), components(input.framework, input), + fragmentTypedefs(input), ]) } diff --git a/packages/houdini-svelte/src/plugin/extract.ts b/packages/houdini-svelte/src/plugin/extract.ts index 5b76f717ed..f7bc2744e6 100644 --- a/packages/houdini-svelte/src/plugin/extract.ts +++ b/packages/houdini-svelte/src/plugin/extract.ts @@ -1,29 +1,21 @@ -import { parseJS, type Maybe, type Script } from 'houdini' +import { parseJS, type Maybe, type Script, find_graphql, Config } from 'houdini' import * as svelte from 'svelte/compiler' -export default async function (filepath: string, contents: string): Promise { +export default async function ( + config: Config, + filepath: string, + contents: string +): Promise { const documents: string[] = [] - let parsedFile = await parseSvelte(contents) if (!parsedFile) { return documents } - // look for any template tag literals in the script body - svelte.walk(parsedFile.script, { - enter(node) { - // if we are looking at the graphql template tag - if ( - node.type === 'TaggedTemplateExpression' && - // @ts-ignore - node.tag.name === 'graphql' - ) { - // @ts-ignore - // parse the tag contents to get the info we need - const printedDoc = node.quasi.quasis[0].value.raw - - documents.push(printedDoc) - } + // look for graphql documents like normal + await find_graphql(config, parsedFile.script, { + tag({ tagContent }) { + documents.push(tagContent) }, }) diff --git a/packages/houdini-svelte/src/plugin/extractLoadFunction.test.ts b/packages/houdini-svelte/src/plugin/extractLoadFunction.test.ts index d73c0ba717..5370fac02e 100644 --- a/packages/houdini-svelte/src/plugin/extractLoadFunction.test.ts +++ b/packages/houdini-svelte/src/plugin/extractLoadFunction.test.ts @@ -24,6 +24,24 @@ describe('extract_load_function', function () { } } \` + `, + expected: { + exports: [houdini_load_fn], + houdini_load: ['Foo'], + }, + }, + { + title: 'handle functions', + source: ` + import { graphql } from '$houdini' + + export const _houdini_load = graphql(\` + query Foo { + viewer { + id + } + } + \`) `, expected: { exports: [houdini_load_fn], @@ -43,6 +61,26 @@ describe('extract_load_function', function () { } \` + export const _houdini_load = store + `, + expected: { + exports: [houdini_load_fn], + houdini_load: ['Foo'], + }, + }, + { + title: 'load single inline identifier as function', + source: ` + import { graphql } from '$houdini' + + const store = graphql(\` + query Foo { + viewer { + id + } + } + \`) + export const _houdini_load = store `, expected: { @@ -231,6 +269,20 @@ describe('extract_load_function', function () { exports: [houdini_after_load_fn, houdini_before_load_fn], }, }, + { + title: 'ignores call expressions inside of functions', + source: ` + fragment(foo, graphql(\` + query MyQuery { + foo + } + \`)) + `, + expected: { + exports: [], + houdini_load: [], + }, + }, ] for (const row of table) { diff --git a/packages/houdini-svelte/src/plugin/extractLoadFunction.ts b/packages/houdini-svelte/src/plugin/extractLoadFunction.ts index 46c4614091..f6ac3641a0 100644 --- a/packages/houdini-svelte/src/plugin/extractLoadFunction.ts +++ b/packages/houdini-svelte/src/plugin/extractLoadFunction.ts @@ -187,6 +187,28 @@ async function processScript( ) } load.push(element.quasi.quasis[0].value.raw) + } else if (element.type === 'CallExpression') { + // if the function is not called graphql, ignore it + if ( + element.callee.type !== 'Identifier' || + element.callee.name !== 'graphql' || + element.arguments.length !== 1 + ) { + throw new Error(`only graphql function can be passed to ${houdini_load_fn}`) + } + let documentString: string + const argument = element.arguments[0] + + // if we have a template or string literal, use its value + if (argument.type === 'TemplateLiteral') { + documentString = argument.quasis[0].value.raw + } else if (argument.type === 'StringLiteral') { + documentString = argument.value + } else { + throw new Error('only strings can be passed to the graphql function') + } + + load.push(documentString) } else if (element.type === 'NewExpression') { const suffix = store_suffix(config) if ( @@ -240,6 +262,25 @@ function identifyQueryReference( if (value.type === 'Identifier' && value.name in imports) { return { local, query: imports[value.name] } } + if ( + value.type === 'CallExpression' && + value.callee.type === 'Identifier' && + value.callee.name in imports + ) { + return { local, query: imports[value.callee.name] } + } + if ( + value.type === 'CallExpression' && + value.callee.type === 'Identifier' && + value.callee.name === 'graphql' && + value.arguments.length === 1 + ) { + if (value.arguments[0].type === 'StringLiteral') { + return { local, query: value.arguments[0].value } + } else if (value.arguments[0].type === 'TemplateLiteral') { + return { local, query: value.arguments[0].quasis[0].value.raw } + } + } if (value.type === 'NewExpression' && value.callee.type == 'Identifier') { return { local, query: imports[value.callee.name] } diff --git a/packages/houdini-svelte/src/plugin/index.ts b/packages/houdini-svelte/src/plugin/index.ts index db2f768a06..4cf35cdca7 100644 --- a/packages/houdini-svelte/src/plugin/index.ts +++ b/packages/houdini-svelte/src/plugin/index.ts @@ -3,7 +3,14 @@ import { HoudiniError, PluginFactory, path, fs } from 'houdini' import generate from './codegen' import extract from './extract' import fs_patch from './fsPatch' -import { plugin_config, resolve_relative, stores_directory, type Framework } from './kit' +import { + plugin_config, + resolve_relative, + stores_directory, + store_name, + store_import_path, + type Framework, +} from './kit' import apply_transforms from './transforms' import validate from './validate' @@ -70,6 +77,24 @@ export const error = svelteKitError }) }, + graphql_tag_return({ config, doc, ensure_import }) { + // if we're supposed to generate a store then add an overloaded declaration + if (doc.generateStore) { + // make sure we are importing the store + const store = store_name({ config, name: doc.name }) + ensure_import({ + identifier: store, + module: store_import_path({ + config, + name: doc.name, + }).replaceAll('$houdini', '..'), + }) + + // and use the store as the return value + return store + } + }, + // we need to add the exports to the index files (this one file processes index.js and index.d.ts) index_file({ config, content, export_star_from, plugin_root }) { const storesDir = diff --git a/packages/houdini-svelte/src/plugin/transforms/kit/load.test.ts b/packages/houdini-svelte/src/plugin/transforms/kit/load.test.ts index b393b745ea..262ee0ddd8 100644 --- a/packages/houdini-svelte/src/plugin/transforms/kit/load.test.ts +++ b/packages/houdini-svelte/src/plugin/transforms/kit/load.test.ts @@ -3,22 +3,55 @@ import { test, expect, describe } from 'vitest' import { route_test } from '../../../test' describe('kit route processor', function () { - test('inline store', async function () { + test('inline function', async function () { const route = await route_test({ component: ` `, }) + expect(route.script).toMatchInlineSnapshot(` + import { load_TestQuery } from "$houdini/plugins/houdini-svelte/stores/TestQuery"; + import { getCurrentConfig } from "$houdini/runtime/lib/config"; + import { RequestContext } from "$houdini/plugins/houdini-svelte/runtime/session"; + import _TestQueryArtifact from "$houdini/artifacts/TestQuery"; + + export async function load(context) { + const houdini_context = new RequestContext(context); + const houdiniConfig = await getCurrentConfig(); + const promises = []; + const inputs = {}; + inputs["TestQuery"] = {}; + + promises.push(load_TestQuery({ + "variables": inputs["TestQuery"], + "event": context, + "blocking": false + })); + + let result = {}; + + try { + result = Object.assign({}, ...(await Promise.all(promises))); + } catch (err) { + throw err; + } + + return { + ...houdini_context.returnValue, + ...result + }; + } + `) expect(route.component).toMatchInlineSnapshot(` export let data; @@ -27,7 +60,7 @@ describe('kit route processor', function () { `) }) - test('inline query', async function () { + test('inline template', async function () { const route = await route_test({ component: ` + `, + }) + + expect(route.component).toMatchInlineSnapshot(` + export let data; + + $: + result = data.TestQuery; + `) + + expect(route.script).toMatchInlineSnapshot(` + import { load_TestQuery } from "$houdini/plugins/houdini-svelte/stores/TestQuery"; + import { getCurrentConfig } from "$houdini/runtime/lib/config"; + import { RequestContext } from "$houdini/plugins/houdini-svelte/runtime/session"; + import _TestQueryArtifact from "$houdini/artifacts/TestQuery"; + + export async function load(context) { + const houdini_context = new RequestContext(context); + const houdiniConfig = await getCurrentConfig(); + const promises = []; + const inputs = {}; + inputs["TestQuery"] = {}; + + promises.push(load_TestQuery({ + "variables": inputs["TestQuery"], + "event": context, + "blocking": false + })); + + let result = {}; + + try { + result = Object.assign({}, ...(await Promise.all(promises))); + } catch (err) { + throw err; + } + + return { + ...houdini_context.returnValue, + ...result + }; + } + `) +}) + test('onError hook', async function () { const route = await route_test({ script: ` diff --git a/packages/houdini-svelte/src/plugin/transforms/query.test.ts b/packages/houdini-svelte/src/plugin/transforms/query.test.ts index 55ab09385c..a37d1980ce 100644 --- a/packages/houdini-svelte/src/plugin/transforms/query.test.ts +++ b/packages/houdini-svelte/src/plugin/transforms/query.test.ts @@ -98,6 +98,68 @@ test('with variables', async function () { `) }) +test('graphql function', async function () { + const route = await component_test( + ` + export function _TestQueryVariables() { + return { + hello: 'world' + } + } + + export let prop1 = 'hello' + export const prop2 = 'goodbye' + export let prop3, prop4 + + const result = graphql(\` + query TestQuery($test: String!) { + users(stringValue: $test) { + id + } + } + \`) + ` + ) + + // make sure we added the right stuff + expect(route).toMatchInlineSnapshot(` + import { TestQueryStore } from "$houdini/plugins/houdini-svelte/stores/TestQuery"; + import { isBrowser } from "$houdini/plugins/houdini-svelte/runtime/adapter"; + import { RequestContext } from "$houdini/plugins/houdini-svelte/runtime/session"; + import { marshalInputs } from "$houdini/runtime/lib/scalars"; + const _houdini_TestQuery = new TestQueryStore(); + + export function _TestQueryVariables() { + return { + hello: "world" + }; + } + + export let prop1 = "hello"; + export const prop2 = "goodbye"; + export let prop3, prop4; + + $: + result = _houdini_TestQuery; + + $: + marshalInputs({ + artifact: _houdini_TestQuery.artifact, + + input: _TestQueryVariables.call(new RequestContext(), { + props: { + prop1: prop1, + prop2: prop2, + prop3: prop3, + prop4: prop4 + } + }) + }).then(_TestQuery_Input => isBrowser && _houdini_TestQuery.fetch({ + variables: _TestQuery_Input + })); + `) +}) + test('missing variables', async function () { vi.spyOn(console, 'error') diff --git a/packages/houdini-svelte/src/plugin/transforms/reactive.test.ts b/packages/houdini-svelte/src/plugin/transforms/reactive.test.ts new file mode 100644 index 0000000000..01a9e4bb94 --- /dev/null +++ b/packages/houdini-svelte/src/plugin/transforms/reactive.test.ts @@ -0,0 +1,69 @@ +import { test, expect } from 'vitest' + +import { component_test } from '../../test' + +test('graphql template tag in a function', async function () { + const route = await component_test( + ` + const result = fragment(user, graphql\` + fragment Foo on Bar { + users(stringValue: $test) { + id + } + } + \`) + ` + ) + + // make sure we added the right stuff + expect(route).toMatchInlineSnapshot(` + import { FooStore } from "$houdini/plugins/houdini-svelte/stores/Foo"; + + $: + result = fragment(user, new FooStore()); + `) +}) + +test('graphql function in a function', async function () { + const route = await component_test( + ` + const result = fragment(user, graphql\` + fragment Foo on Bar { + users(stringValue: $test) { + id + } + } + \`) + ` + ) + + // make sure we added the right stuff + expect(route).toMatchInlineSnapshot(` + import { FooStore } from "$houdini/plugins/houdini-svelte/stores/Foo"; + + $: + result = fragment(user, new FooStore()); + `) +}) + +test('graphql function in a function', async function () { + const route = await component_test( + ` + const result = fragment(user, graphql(\` + fragment Foo on Bar { + users(stringValue: $test) { + id + } + } + \`)) + ` + ) + + // make sure we added the right stuff + expect(route).toMatchInlineSnapshot(` + import { FooStore } from "$houdini/plugins/houdini-svelte/stores/Foo"; + + $: + result = fragment(user, new FooStore()); + `) +}) diff --git a/packages/houdini-svelte/src/plugin/transforms/reactive.ts b/packages/houdini-svelte/src/plugin/transforms/reactive.ts index d8a1d9ef38..fa6c0a16a6 100644 --- a/packages/houdini-svelte/src/plugin/transforms/reactive.ts +++ b/packages/houdini-svelte/src/plugin/transforms/reactive.ts @@ -15,15 +15,9 @@ type TaggedTemplateExpression = recast.types.namedTypes.TaggedTemplateExpression export default async function ReactiveProcessor(config: Config, page: SvelteTransformPage) { // if a file imports graphql from $houdini then they might have an inline document // that needs to be transformed into a reactive statement. - // in order to avoid situations where graphql`` is passed around to functions we are going to - // look for graphql`` being passed specifically to a function that matches some list - // being used as an assignemtn - // - // ie: - // - // const value = graphql`` -> $: value = query(graphql``) - // const { value } = graphql`` -> $: { value } = query(graphql``) - // + // in order to avoid situations where graphql is passed around to functions we are going to + // look for graphql being passed specifically to a function that matches some list + // being used as an assignment if ( !is_component(config, page.framework, page.filepath) && !is_route(config, page.framework, page.filepath) @@ -32,7 +26,7 @@ export default async function ReactiveProcessor(config: Config, page: SvelteTran } // look for the list of magic functions the user has imported - const magicFunctions = ['query', 'graphql', 'fragment', 'paginatedFragment', 'paginatedQuery'] + const magicFunctions = ['graphql', 'fragment', 'paginatedFragment'] // if they didn't import graphql and at least something else, there's nothing to do if (!magicFunctions.includes('graphql') || magicFunctions.length === 1) { @@ -98,15 +92,34 @@ function filterCallExpr(expr: CallExpression) { // that matches a magic function that was imported from the runtime return } - const callExpr = expr as CallExpression + + // if the name of the function is 'graphql' then we should look for a string or + // template literal + if ( + expr.callee.type === 'Identifier' && + expr.callee.name === 'graphql' && + expr.arguments.length === 1 && + (expr.arguments[0].type === 'StringLiteral' || expr.arguments[0].type === 'TemplateLiteral') + ) { + return true + } // one of the arguments to the function must be a tagged template literal - // with the graphql tag - const tag = callExpr.arguments.find( + // with the graphql tag or a function named graphql with a string or template + // literal + const tag = expr.arguments.find( (arg) => - arg.type === 'TaggedTemplateExpression' && - arg.tag.type === 'Identifier' && - arg.tag.name === 'graphql' + // if one of the arguments is a graphql template tag + (arg.type === 'TaggedTemplateExpression' && + arg.tag.type === 'Identifier' && + arg.tag.name === 'graphql') || + // or an graphql function with a string or template literal + (arg.type === 'CallExpression' && + arg.callee.type === 'Identifier' && + arg.callee.name === 'graphql' && + arg.arguments.length === 1 && + (arg.arguments[0].type === 'StringLiteral' || + arg.arguments[0].type === 'TemplateLiteral')) ) if (!tag) { return diff --git a/packages/houdini-svelte/src/plugin/transforms/tags.ts b/packages/houdini-svelte/src/plugin/transforms/tags.ts index 81fd3af08f..3bcd699c06 100644 --- a/packages/houdini-svelte/src/plugin/transforms/tags.ts +++ b/packages/houdini-svelte/src/plugin/transforms/tags.ts @@ -7,7 +7,7 @@ import { SvelteTransformPage } from './types' const AST = recast.types.builders export default async function GraphQLTagProcessor(config: Config, page: SvelteTransformPage) { - // all graphql template tags need to be turned into a reference to the appropriate store + // all graphql documents need to be turned into a reference to the appropriate store await find_graphql(config, page.script, { dependency: page.watch_file, tag(tag) { diff --git a/packages/houdini/src/codegen/generators/indexFile/indexFile.test.ts b/packages/houdini/src/codegen/generators/indexFile/indexFile.test.ts index 364e86b1a8..24890e4a73 100644 --- a/packages/houdini/src/codegen/generators/indexFile/indexFile.test.ts +++ b/packages/houdini/src/codegen/generators/indexFile/indexFile.test.ts @@ -24,6 +24,7 @@ test('index file - esm', async function () { // open up the index file const queryContents = await fs.readFile(path.join(config.artifactDirectory, 'index.js')) + console.log({ queryContents }) expect(queryContents).toBeTruthy() // parse the contents const parsedQuery: ProgramKind = recast.parse(queryContents!, { diff --git a/packages/houdini/src/codegen/generators/runtime/index.ts b/packages/houdini/src/codegen/generators/runtime/index.ts index a8b15c3494..cff6f02e53 100644 --- a/packages/houdini/src/codegen/generators/runtime/index.ts +++ b/packages/houdini/src/codegen/generators/runtime/index.ts @@ -1,6 +1,20 @@ -import { Config, siteURL as SITE_URL, fs, HoudiniError, path, houdini_mode } from '../../../lib' +import * as recast from 'recast' -export default async function runtimeGenerator(config: Config) { +import { + Config, + siteURL as SITE_URL, + fs, + HoudiniError, + path, + houdini_mode, + CollectedGraphQLDocument, + parseJS, + ensureImports, +} from '../../../lib' + +const AST = recast.types.builders + +export default async function runtimeGenerator(config: Config, docs: CollectedGraphQLDocument[]) { // generate the adapter to normalize interactions with the framework // update the generated runtime to point to the client await Promise.all([ @@ -22,6 +36,80 @@ export default async function runtimeGenerator(config: Config) { .filter((plugin) => plugin.include_runtime) .map((plugin) => generatePluginRuntime(config, plugin)), ]) + + // we need to find the index of the `export default function graphql` in the index.d.ts of the runtime + const indexPath = path.join(config.runtimeDirectory, 'index.d.ts') + const contents = await parseJS((await fs.readFile(indexPath)) || '') + + // figure out if any of the plugins provide a graphql tag export + const graphql_tag_return = config.plugins.find( + (plugin) => plugin.graphql_tag_return + )?.graphql_tag_return + if (graphql_tag_return && contents) { + // build up the mapping of hard coded strings to exports + const overloaded_returns: Record = {} + for (const doc of docs) { + const return_value = graphql_tag_return!({ + config, + doc, + ensure_import({ identifier, module }) { + ensureImports({ + config, + body: contents.script.body, + sourceModule: module, + import: [identifier], + }) + }, + }) + if (return_value) { + overloaded_returns[doc.originalString] = return_value + } + } + + // if we have any overloaded return values then we need to update the index.d.ts of the + // runtime to return those values + if (Object.keys(overloaded_returns).length > 0) { + for (const [i, expression] of (contents?.script.body ?? []).entries()) { + if ( + expression.type !== 'ExportNamedDeclaration' || + expression.declaration?.type !== 'TSDeclareFunction' || + expression.declaration.id?.name !== 'graphql' + ) { + continue + } + + // we need to insert an overloaded definition for every entry we found + for (const [queryString, returnValue] of Object.entries(overloaded_returns)) { + // build up the input with the query string as a hard coded value + const input = AST.identifier('str') + input.typeAnnotation = AST.tsTypeAnnotation( + AST.tsLiteralType(AST.stringLiteral(queryString)) + ) + + // it should return the right thing + contents?.script.body.splice( + i, + 0, + AST.exportNamedDeclaration( + AST.tsDeclareFunction( + AST.identifier('graphql'), + [input], + AST.tsTypeAnnotation( + AST.tsTypeReference(AST.identifier(returnValue)) + ) + ) + ) + ) + } + + // we're done here + break + } + + // write the result back to the file + await fs.writeFile(indexPath, recast.prettyPrint(contents!.script).code) + } + } } async function generatePluginRuntime(config: Config, plugin: Config['plugins'][number]) { diff --git a/packages/houdini/src/codegen/index.ts b/packages/houdini/src/codegen/index.ts index da1bb11684..d24e67beab 100755 --- a/packages/houdini/src/codegen/index.ts +++ b/packages/houdini/src/codegen/index.ts @@ -100,7 +100,6 @@ export async function runPipeline(config: Config, docs: CollectedGraphQLDocument docs ) } catch (e) { - console.log(e) error = e as Error } @@ -189,8 +188,8 @@ async function collectDocuments(config: Config): Promise [content] - const javascript_extractor = (filepath: string, content: string) => + const graphql_extractor = (config: Config, filepath: string, content: string) => [content] + const javascript_extractor = (fconfig: Config, ilepath: string, content: string) => processJSFile(config, content) extractors['.ts'].push(javascript_extractor) extractors['.js'].push(javascript_extractor) @@ -222,13 +221,13 @@ async function collectDocuments(config: Config): Promise 0) { documents.push(...found.map((document) => ({ filepath, document }))) } diff --git a/packages/houdini/src/lib/config.ts b/packages/houdini/src/lib/config.ts index eff2d02613..14e248b43b 100644 --- a/packages/houdini/src/lib/config.ts +++ b/packages/houdini/src/lib/config.ts @@ -939,10 +939,19 @@ export type Plugin = { extensions?: string[] transform_runtime?: Record string> after_load?: (config: Config) => Promise | void - extract_documents?: (filepath: string, content: string) => Promise | string[] + extract_documents?: ( + config: Config, + filepath: string, + content: string + ) => Promise | string[] generate?: GenerateHook transform_file?: (page: TransformPage) => Promise<{ code: string }> | { code: string } index_file?: ModuleIndexTransform + graphql_tag_return?: (args: { + config: Config + doc: CollectedGraphQLDocument + ensure_import: (import_args: { identifier: string; module: string }) => void + }) => string | undefined validate?: (args: { config: Config documents: CollectedGraphQLDocument[] diff --git a/packages/houdini/src/lib/fs.ts b/packages/houdini/src/lib/fs.ts index 8fdf435e33..8325ad7dc6 100644 --- a/packages/houdini/src/lib/fs.ts +++ b/packages/houdini/src/lib/fs.ts @@ -99,8 +99,9 @@ export async function writeFile(filepath: string, data: string) { return } - // no mock in tests + // write the file when testing if (houdini_mode.is_testing) { + memfs.mkdirpSync(path.dirname(filepath)) return memfs.writeFileSync(filepath, data) } diff --git a/packages/houdini/src/lib/walk.ts b/packages/houdini/src/lib/walk.ts index 10a5abf96b..ce37dbd01d 100644 --- a/packages/houdini/src/lib/walk.ts +++ b/packages/houdini/src/lib/walk.ts @@ -1,4 +1,4 @@ -import type { TaggedTemplateExpressionKind, IdentifierKind } from 'ast-types/lib/gen/kinds' +import type { TaggedTemplateExpressionKind, CallExpressionKind } from 'ast-types/lib/gen/kinds' import { asyncWalk, BaseNode } from 'estree-walker' import * as graphql from 'graphql' @@ -40,65 +40,101 @@ export async function find_graphql( ): Promise { await asyncWalk(parsedScript!, { async enter(node, parent) { - // if we are looking at the graphql template tag - if ( - node.type === 'TaggedTemplateExpression' && - ((node as TaggedTemplateExpressionKind).tag as IdentifierKind).name === 'graphql' - ) { - const expr = node as TaggedTemplateExpressionKind - // we're going to replace the tag with something the runtime can use + // graphql documents come in a few forms: + // - graphql template tags + // - strings passed to graphql function + if (node.type !== 'TaggedTemplateExpression' && node.type !== 'CallExpression') { + return + } - // first, lets parse the tag contents to get the info we need - const tagContent = expr.quasi.quasis[0].value.raw - const parsedTag = graphql.parse(tagContent) + let documentString: string + + // process template tags + if (node.type === 'TaggedTemplateExpression') { + // grab the string passed to the template tag as the document + const expr = node as TaggedTemplateExpressionKind - // if there is a predicate and the graphql tag does not satisfy it - if (walker.where && !walker.where(parsedTag)) { - // ignore the tag + // we only care about graphql template tags + if (expr.tag.type !== 'Identifier' || expr.tag.name !== 'graphql') { return } - // pull out the name of the thing - const definition = config.extractDefinition(parsedTag) as - | graphql.OperationDefinitionNode - | graphql.FragmentDefinitionNode - const name = definition.name?.value - if (!name) { - throw new Error('Could not find definition name') + documentString = expr.quasi.quasis[0].value.raw + } + // process function calls + else if (node.type === 'CallExpression') { + const expr = node as CallExpressionKind + // if the function is not called graphql, ignore it + if ( + expr.callee.type !== 'Identifier' || + expr.callee.name !== 'graphql' || + expr.arguments.length !== 1 + ) { + return } - let kind: CompiledDocumentKind - if (definition.kind === 'FragmentDefinition') { - kind = CompiledFragmentKind + const argument = expr.arguments[0] + + // if we have a template or string literal, use its value + if (argument.type === 'TemplateLiteral') { + documentString = argument.quasis[0].value.raw + } else if (argument.type === 'StringLiteral') { + documentString = argument.value } else { - if (definition.operation === 'query') { - kind = CompiledQueryKind - } else if (definition.operation === 'mutation') { - kind = CompiledMutationKind - } else { - kind = CompiledSubscriptionKind - } + return } + } else { + return + } + + // if we got this far, {documentString} holds the query + const parsedTag = graphql.parse(documentString) - // tell the walk there was a dependency - walker.dependency?.(config.artifactPath(parsedTag)) + // if there is a predicate and the graphql tag does not satisfy it + if (walker.where && !walker.where(parsedTag)) { + // ignore the tag + return + } - // invoker the walker's callback with the right context - await walker.tag({ - parsedDocument: parsedTag, - node: { - ...node, - ...this, - remove: this.remove, - replaceWith: this.replace, - }, - artifact: { - name, - kind, - }, - parent, - tagContent, - }) + // pull out the name of the thing + const definition = config.extractDefinition(parsedTag) as + | graphql.OperationDefinitionNode + | graphql.FragmentDefinitionNode + const name = definition.name?.value + if (!name) { + throw new Error('Could not find definition name') + } + let kind: CompiledDocumentKind + if (definition.kind === 'FragmentDefinition') { + kind = CompiledFragmentKind + } else { + if (definition.operation === 'query') { + kind = CompiledQueryKind + } else if (definition.operation === 'mutation') { + kind = CompiledMutationKind + } else { + kind = CompiledSubscriptionKind + } } + + // tell the walk there was a dependency + walker.dependency?.(config.artifactPath(parsedTag)) + + // invoker the walker's callback with the right context + await walker.tag({ + parsedDocument: parsedTag, + node: { + ...node, + ...this, + remove: this.remove, + replaceWith: this.replace, + }, + artifact: { + name, + kind, + }, + parent, + tagContent: documentString, + }) }, }) } diff --git a/packages/houdini/src/runtime/index.ts b/packages/houdini/src/runtime/index.ts index 9dd64410f8..5b5667199c 100644 --- a/packages/houdini/src/runtime/index.ts +++ b/packages/houdini/src/runtime/index.ts @@ -1,5 +1,3 @@ -// this template tag gets removed by the preprocessor so it should never be invoked. -// this function must return any so that we can assign it a type in a variable declaration (ie an inline store) import _cache from './cache' import { Cache as InternalCache } from './cache/cache' import type { CacheTypeDef } from './generated' @@ -7,8 +5,10 @@ import { Cache } from './public' export * from './lib' +// this template tag gets removed by the preprocessor so it should never be invoked. +// this function must return any so that we can assign it a type in a variable declaration (ie an inline store) // ideally we would be able to parse the input for values but typescript does not yet support that kind of matches in template args -export function graphql(str: TemplateStringsArray): any { +export function graphql(str: string | TemplateStringsArray): any { // if we are executing this function as part of the plugin, we need to return // the query instead of throwing an error. We don't want to bundle the graphql // module into the runtime so all we can do is return the query string diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5183779d02..70bfe2de62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,11 +49,11 @@ importers: graphql-ws: ^5.8.2 ws: ^8.8.1 dependencies: - '@graphql-yoga/node': 2.13.13_graphql@15.8.0 + '@graphql-yoga/node': 2.13.13_graphql@16.6.0 '@kitql/helper': 0.5.0 - graphql: 15.8.0 - graphql-relay: 0.10.0_graphql@15.8.0 - graphql-ws: 5.11.2_graphql@15.8.0 + graphql: 16.6.0 + graphql-relay: 0.10.0_graphql@16.6.0 + graphql-ws: 5.11.2_graphql@16.6.0 ws: 8.11.0 e2e/next: @@ -1294,6 +1294,35 @@ packages: '@envelop/types': 2.4.0_graphql@15.8.0 graphql: 15.8.0 tslib: 2.4.0 + dev: true + + /@envelop/core/2.6.0_graphql@16.6.0: + resolution: + { + integrity: sha512-yTptKinJN//i6m1kXUbnLBl/FobzddI4ehURAMS08eRUOQwAuXqJU9r8VdTav8nIZLb4t6cuDWFb3n331LiwLw==, + } + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + '@envelop/types': 2.4.0_graphql@16.6.0 + graphql: 16.6.0 + tslib: 2.4.0 + dev: false + + /@envelop/parser-cache/4.7.0_4hr55tbjlvoppd2sokdhrbpreq: + resolution: + { + integrity: sha512-63NfXDcW/vGn4U6NFxaZ0JbYWAcJb9A6jhTvghsSz1ZS+Dny/ci8bVSgVmM1q+N56hPyGsVPuyI+rIc71mPU5g==, + } + peerDependencies: + '@envelop/core': ^2.6.0 + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + '@envelop/core': 2.6.0_graphql@16.6.0 + graphql: 16.6.0 + lru-cache: 6.0.0 + tslib: 2.4.1 + dev: false /@envelop/parser-cache/4.7.0_nhznfxrlclsvs4aen6pcdf2xd4: resolution: @@ -1308,6 +1337,7 @@ packages: graphql: 15.8.0 lru-cache: 6.0.0 tslib: 2.4.1 + dev: true /@envelop/types/2.4.0_graphql@15.8.0: resolution: @@ -1319,6 +1349,34 @@ packages: dependencies: graphql: 15.8.0 tslib: 2.4.1 + dev: true + + /@envelop/types/2.4.0_graphql@16.6.0: + resolution: + { + integrity: sha512-pjxS98cDQBS84X29VcwzH3aJ/KiLCGwyMxuj7/5FkdiaCXAD1JEvKEj9LARWlFYj1bY43uII4+UptFebrhiIaw==, + } + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + graphql: 16.6.0 + tslib: 2.4.1 + dev: false + + /@envelop/validation-cache/4.7.0_4hr55tbjlvoppd2sokdhrbpreq: + resolution: + { + integrity: sha512-PzL+GfWJRT+JjsJqZAIxHKEkvkM3hxkeytS5O0QLXT8kURNBV28r+Kdnn2RCF5+6ILhyGpiDb60vaquBi7g4lw==, + } + peerDependencies: + '@envelop/core': ^2.6.0 + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + '@envelop/core': 2.6.0_graphql@16.6.0 + graphql: 16.6.0 + lru-cache: 6.0.0 + tslib: 2.4.1 + dev: false /@envelop/validation-cache/4.7.0_nhznfxrlclsvs4aen6pcdf2xd4: resolution: @@ -1333,6 +1391,7 @@ packages: graphql: 15.8.0 lru-cache: 6.0.0 tslib: 2.4.1 + dev: true /@esbuild/android-arm/0.15.18: resolution: @@ -1629,6 +1688,19 @@ packages: graphql: 15.8.0 tslib: 2.4.1 + /@graphql-tools/merge/8.3.14_graphql@16.6.0: + resolution: + { + integrity: sha512-zV0MU1DnxJLIB0wpL4N3u21agEiYFsjm6DI130jqHpwF0pR9HkF+Ni65BNfts4zQelP0GjkHltG+opaozAJ1NA==, + } + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-tools/utils': 9.1.3_graphql@16.6.0 + graphql: 16.6.0 + tslib: 2.4.1 + dev: false + /@graphql-tools/schema/9.0.12_graphql@15.8.0: resolution: { @@ -1643,6 +1715,21 @@ packages: tslib: 2.4.1 value-or-promise: 1.0.11 + /@graphql-tools/schema/9.0.12_graphql@16.6.0: + resolution: + { + integrity: sha512-DmezcEltQai0V1y96nwm0Kg11FDS/INEFekD4nnVgzBqawvznWqK6D6bujn+cw6kivoIr3Uq//QmU/hBlBzUlQ==, + } + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-tools/merge': 8.3.14_graphql@16.6.0 + '@graphql-tools/utils': 9.1.3_graphql@16.6.0 + graphql: 16.6.0 + tslib: 2.4.1 + value-or-promise: 1.0.11 + dev: false + /@graphql-tools/utils/8.13.1_graphql@15.8.0: resolution: { @@ -1653,6 +1740,19 @@ packages: dependencies: graphql: 15.8.0 tslib: 2.4.1 + dev: true + + /@graphql-tools/utils/8.13.1_graphql@16.6.0: + resolution: + { + integrity: sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==, + } + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + graphql: 16.6.0 + tslib: 2.4.1 + dev: false /@graphql-tools/utils/9.1.3_graphql@15.8.0: resolution: @@ -1665,6 +1765,18 @@ packages: graphql: 15.8.0 tslib: 2.4.1 + /@graphql-tools/utils/9.1.3_graphql@16.6.0: + resolution: + { + integrity: sha512-bbJyKhs6awp1/OmP+WKA1GOyu9UbgZGkhIj5srmiMGLHohEOKMjW784Sk0BZil1w2x95UPu0WHw6/d/HVCACCg==, + } + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + graphql: 16.6.0 + tslib: 2.4.1 + dev: false + /@graphql-typed-document-node/core/3.1.1_graphql@15.8.0: resolution: { @@ -1674,6 +1786,18 @@ packages: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 dependencies: graphql: 15.8.0 + dev: true + + /@graphql-typed-document-node/core/3.1.1_graphql@16.6.0: + resolution: + { + integrity: sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==, + } + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + graphql: 16.6.0 + dev: false /@graphql-yoga/common/2.12.12_graphql@15.8.0: resolution: @@ -1696,6 +1820,30 @@ packages: tslib: 2.4.1 transitivePeerDependencies: - encoding + dev: true + + /@graphql-yoga/common/2.12.12_graphql@16.6.0: + resolution: + { + integrity: sha512-La2ygIw2qlIJZrRGT4nW70Nam7gQ2xZkOn0FDCnKWSJhQ4nHw4aFAkeHIJdZGK0u2TqtXRrNSAj5cb/TZoqUiQ==, + } + peerDependencies: + graphql: ^15.2.0 || ^16.0.0 + dependencies: + '@envelop/core': 2.6.0_graphql@16.6.0 + '@envelop/parser-cache': 4.7.0_4hr55tbjlvoppd2sokdhrbpreq + '@envelop/validation-cache': 4.7.0_4hr55tbjlvoppd2sokdhrbpreq + '@graphql-tools/schema': 9.0.12_graphql@16.6.0 + '@graphql-tools/utils': 8.13.1_graphql@16.6.0 + '@graphql-typed-document-node/core': 3.1.1_graphql@16.6.0 + '@graphql-yoga/subscription': 2.2.3 + '@whatwg-node/fetch': 0.3.2 + dset: 3.1.2 + graphql: 16.6.0 + tslib: 2.4.1 + transitivePeerDependencies: + - encoding + dev: false /@graphql-yoga/node/2.13.13_graphql@15.8.0: resolution: @@ -1714,6 +1862,26 @@ packages: tslib: 2.4.1 transitivePeerDependencies: - encoding + dev: true + + /@graphql-yoga/node/2.13.13_graphql@16.6.0: + resolution: + { + integrity: sha512-3NmdEq3BkuVLRbo5yUi401sBiwowSKgY8O1DN1RwYdHRr0nu2dXzlYEETf4XLymyP6mKsVfQgsy7HQjwsc1oNw==, + } + peerDependencies: + graphql: ^15.2.0 || ^16.0.0 + dependencies: + '@envelop/core': 2.6.0_graphql@16.6.0 + '@graphql-tools/utils': 8.13.1_graphql@16.6.0 + '@graphql-yoga/common': 2.12.12_graphql@16.6.0 + '@graphql-yoga/subscription': 2.2.3 + '@whatwg-node/fetch': 0.3.2 + graphql: 16.6.0 + tslib: 2.4.1 + transitivePeerDependencies: + - encoding + dev: false /@graphql-yoga/subscription/2.2.3: resolution: @@ -5957,7 +6125,7 @@ packages: integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==, } - /graphql-relay/0.10.0_graphql@15.8.0: + /graphql-relay/0.10.0_graphql@16.6.0: resolution: { integrity: sha512-44yBuw2/DLNEiMypbNZBt1yMDbBmyVPVesPywnteGGALiBmdyy1JP8jSg8ClLePg8ZZxk0O4BLhd1a6U/1jDOQ==, @@ -5966,7 +6134,7 @@ packages: peerDependencies: graphql: ^16.2.0 dependencies: - graphql: 15.8.0 + graphql: 16.6.0 dev: false /graphql-tag/2.12.6_graphql@15.8.0: @@ -5994,6 +6162,18 @@ packages: graphql: 15.8.0 dev: false + /graphql-ws/5.11.2_graphql@16.6.0: + resolution: + { + integrity: sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w==, + } + engines: { node: '>=10' } + peerDependencies: + graphql: '>=0.11 <=16' + dependencies: + graphql: 16.6.0 + dev: false + /graphql/15.8.0: resolution: { @@ -6001,6 +6181,14 @@ packages: } engines: { node: '>= 10.x' } + /graphql/16.6.0: + resolution: + { + integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==, + } + engines: { node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0 } + dev: false + /hard-rejection/2.1.0: resolution: { diff --git a/site/src/routes/+page.svelte b/site/src/routes/+page.svelte index 02c35ceb24..a56d8c77f0 100644 --- a/site/src/routes/+page.svelte +++ b/site/src/routes/+page.svelte @@ -6,13 +6,13 @@ @@ -104,13 +104,13 @@ Fragments may also contain a paginated field, similar to queries. A paginated fr // the reference will get passed as a prop export let user - $: friendList = paginatedFragment(user, graphql` + $: friendList = paginatedFragment(user, graphql(` fragment UserWithFriends on User { friends(first: 10) @paginate { name } } - `) + `))