Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query outside routes #95

Merged
merged 31 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9618c75
split up module from framework config
AlecAivazis May 22, 2021
da6030f
move test config defn to bottom of file
AlecAivazis May 22, 2021
65f8171
tests pass
AlecAivazis May 23, 2021
9e79836
internal api change
AlecAivazis May 23, 2021
6040129
fixed build errors for init command
AlecAivazis May 24, 2021
6a29572
dry up config file generator
AlecAivazis May 24, 2021
2a42598
merge
AlecAivazis May 24, 2021
9bca5f5
incorporate recent init changes
AlecAivazis May 24, 2021
288c405
dry preprocessor tests
AlecAivazis May 25, 2021
d396694
cleanup
AlecAivazis May 25, 2021
bb1d0a4
Merge branch 'main' into query-outside-routes
AlecAivazis May 25, 2021
b0830c7
rename cache internals
AlecAivazis May 28, 2021
6bbcdd0
distinguish routes from components
AlecAivazis May 30, 2021
14bbe44
wrap non-route queries in a component-only wrapper
AlecAivazis Jun 12, 2021
db59acd
merge
AlecAivazis Jun 12, 2021
79fd1c8
first pass at firing a query on mount
AlecAivazis Jun 12, 2021
35d99e6
added comment
AlecAivazis Jun 12, 2021
dbf83ac
better handling of null variable function
AlecAivazis Jun 12, 2021
3b8955c
add tests for component queries inside routes in a bare svelte app
AlecAivazis Jun 12, 2021
0a51f44
cleanup typedefs
AlecAivazis Jun 13, 2021
5257e8d
remove lastKnownVariables and fix lasting issue
AlecAivazis Jun 13, 2021
30d8ac3
deep-equal is not esm compatible
AlecAivazis Jun 13, 2021
c023cd3
fix bug causing stale variables to be used when updating a store's data
AlecAivazis Jun 13, 2021
5e34fba
merge
AlecAivazis Jun 13, 2021
97ef9ad
remove unused config
AlecAivazis Jun 13, 2021
656223e
tests pass
AlecAivazis Jun 13, 2021
cc931cf
remove unused import in example
AlecAivazis Jun 13, 2021
60c25c1
move variable computation into a separate reactive statement
AlecAivazis Jun 15, 2021
0ef64ab
document loading state
AlecAivazis Jun 15, 2021
2340069
update example config file
AlecAivazis Jun 15, 2021
7a026fb
add section for bare svelte apps and spa mode
AlecAivazis Jun 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ for the generation of an incredibly lean GraphQL abstraction for your applicatio
1. [Configuring Your Application](#configuring-your-application)
1. [Sapper](#sapper)
1. [SvelteKit](#sveltekit)
1. [Svelte](#svelte)
1. [Running the Compiler](#running-the-compiler)
1. [Fetching Data](#fetching-data)
1. [Query variables and page data](#query-variables-and-page-data)
1. [Loading State](#loading-state)
1. [Refetching Data](#refetching-data)
1. [What about load?](#what-about-load)
1. [Fragments](#fragments)
Expand Down Expand Up @@ -165,6 +167,13 @@ If you have updated your schema on the server, you can pull down the most recent
npx houdini generate --pull-schema
```

### Svelte

If you are working on an application that isn't using SvelteKit or Sapper, you have to configure the
compiler and preprocessor to generate the correct logic by setting the `framework` field in your
config file to `"svelte"`. You should also use this setting if you are building a SvelteKit application
in SPA mode.

## 🚀  Fetching Data

Grabbing data from your API is done with the `query` function:
Expand All @@ -189,11 +198,6 @@ Grabbing data from your API is done with the `query` function:
{/each}
```

Please note that since `query` desugars into a `load` function (see [below](#what-about-load)) you
can only use it inside of a page or layout. This will be addressed soon. Until then, you will need
to only use `query` inside of components defined under `/src/routes`.


### Query variables and page data

At the moment, query variables are declared as a function in the module context of your component.
Expand Down Expand Up @@ -241,6 +245,52 @@ modified example from the [demo](./example):
{/each}
```

### Loading State

The methods used for tracking the loading state of your queries changes depending
on the context of your component. For queries that live in routes (ie, in
`/src/routes/...`), the actual query happens in a `load` function as described
in [What about load?](#what-about-load). Because of this, the best way to track
if your query is loading is to use the
[navigating store](https://kit.svelte.dev/docs#modules-$app-stores) exported from `$app/stores`:

```svelte
// src/routes/index.svelte

<script>
import { query } from '$houdini'
import { navigating } from '$app/stores'

const { data } = query(...)
</script>

{#if $navigating}
loading...
{:else}
data is loaded!
{/if}
```

However, since queries inside of non-route components (ie, ones that are not defined in `/src/routes/...`)
do not get hoisted to a `load` function, the recommended practice to is use the store returned from
the result of query:

```svelte
// src/components/MyComponent.svelte

<script>
import { query } from '$houdini'

const { data, loading } = query(...)
</script>

{#if $loading}
loading...
{:else}
data is loaded!
{/if}
```

### Refetching Data

Refetching data is done with the `refetch` function provided from the result of a query:
Expand Down
3 changes: 2 additions & 1 deletion example/houdini.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path'
export default {
schemaPath: path.resolve('./schema/schema.gql'),
sourceGlob: 'src/**/*.svelte',
mode: 'kit',
framework: 'kit',
module: 'esm',
apiUrl: 'http://localhost:4000/graphql',
}
116 changes: 74 additions & 42 deletions packages/houdini-common/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,36 @@ export type ConfigFile = {
schemaPath?: string
schema?: string
quiet?: boolean
verifyHash?: boolean
apiUrl?: string
// an old config file could specify mode instead of framework and module
mode?: 'kit' | 'sapper'
framework?: 'kit' | 'sapper' | 'svelte'
module?: 'esm' | 'commonjs'
}

// a place to hold conventions and magic strings
export class Config {
filepath: string
rootDir: string
projectRoot: string
schema: graphql.GraphQLSchema
apiUrl?: string
schemaPath?: string
sourceGlob: string
quiet: boolean
verifyHash: boolean
mode: string = 'sapper'
framework: 'sapper' | 'kit' | 'svelte' = 'sapper'
module: 'commonjs' | 'esm' = 'commonjs'

constructor({
schema,
schemaPath,
sourceGlob,
apiUrl,
quiet = false,
verifyHash,
filepath,
mode = 'sapper',
framework = 'sapper',
module = 'commonjs',
mode,
}: ConfigFile & { filepath: string }) {
// make sure we got some kind of schema
if (!schema && !schemaPath) {
Expand All @@ -53,23 +57,47 @@ export class Config {
)
}

// if we were given a mode instead of framework/module
if (mode) {
if (!quiet) {
// warn the user
console.warn('Encountered deprecated config value: mode')
console.warn(
'This parameter will be removed in a future version. Please update your config with the following values:'
)
}
if (mode === 'sapper') {
if (!quiet) {
console.warn(JSON.stringify({ framework: 'sapper', module: 'commonjs' }))
}
framework = 'sapper'
module = 'commonjs'
} else {
if (!quiet) {
console.warn(JSON.stringify({ framework: 'kit', module: 'esm' }))
}
framework = 'kit'
module = 'esm'
}
}

// save the values we were given
this.schemaPath = schemaPath
this.apiUrl = apiUrl
this.filepath = filepath
this.sourceGlob = sourceGlob
this.quiet = quiet
this.verifyHash = typeof verifyHash === 'undefined' ? true : verifyHash
this.mode = mode
this.framework = framework
this.module = module
this.projectRoot = path.dirname(filepath)

// if we are building a sapper project, we want to put the runtime in
// src/node_modules so that we can access @sapper/app and interact
// with the application stores directly
const rootDir = path.dirname(filepath)
this.rootDir =
mode === 'sapper'
? path.join(rootDir, 'src', 'node_modules', '$houdini')
: path.join(rootDir, '$houdini')
framework === 'sapper'
? path.join(this.projectRoot, 'src', 'node_modules', '$houdini')
: path.join(this.projectRoot, '$houdini')
}

/*
Expand Down Expand Up @@ -288,8 +316,39 @@ export class Config {
throw new Error('Could not find connection name from fragment: ' + fragmentName)
}
}
// a place to store the current configuration
let _config: Config

export function testConfig(config: {} = {}) {
// get the project's current configuration
export async function getConfig(): Promise<Config> {
if (_config) {
return _config
}

// load the config file
const configPath = path.join(process.cwd(), 'houdini.config.js')

// on windows, we need to prepend the right protocol before we
// can import from an absolute path
let importPath = configPath
if (os.platform() === 'win32') {
importPath = 'file:///' + importPath
}

const imported = await import(importPath)

// if this is wrapped in a default, use it
const config = imported.default || imported

// add the filepath and save the result
_config = new Config({
...config,
filepath: configPath,
})
return _config
}

export function testConfig(config: Partial<ConfigFile> = {}) {
return new Config({
filepath: path.join(process.cwd(), 'config.cjs'),
sourceGlob: '123',
Expand Down Expand Up @@ -357,39 +416,12 @@ export function testConfig(config: {} = {}) {
cat: Cat
}
`,
framework: 'sapper',
quiet: true,
...config,
})
}

// a place to store the current configuration
let _config: Config

// get the project's current configuration
export async function getConfig(): Promise<Config> {
if (_config) {
return _config
}

// load the config file
const configPath = path.join(process.cwd(), 'houdini.config.js')

// on windows, we need to prepend the right protocol before we
// can import from an absolute path
let importPath = configPath
if (os.platform() === 'win32') {
importPath = 'file:///' + importPath
}

const imported = await import(importPath)

// if this is wrapped in a default, use it
const config = imported.default || imported

// add the filepath and save the result
_config = new Config({
...config,
filepath: configPath,
})
return _config
type Partial<T> = {
[P in keyof T]?: T[P]
}
36 changes: 2 additions & 34 deletions packages/houdini-preprocess/src/transforms/fragment.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// external imports
import * as svelte from 'svelte/compiler'
// local imports
import fragmentProcessor from './fragment'
import { testConfig } from 'houdini-common'
import { preprocessorTest } from '../utils'
import '../../../../jest.setup'

describe('fragment preprocessor', function () {
Expand All @@ -20,7 +18,7 @@ describe('fragment preprocessor', function () {
`)

// make sure we added the right stuff
expect(doc.instance.content).toMatchInlineSnapshot(`
expect(doc.instance?.content).toMatchInlineSnapshot(`
import _TestFragmentArtifact from "$houdini/artifacts/TestFragment";
let reference;

Expand All @@ -31,33 +29,3 @@ describe('fragment preprocessor', function () {
`)
})
})

async function preprocessorTest(content: string) {
const schema = `
type User {
id: ID!
}

`

// parse the document
const parsed = svelte.parse(content)

// build up the document we'll pass to the processor
const config = testConfig({ schema, verifyHash: false })

const doc = {
instance: parsed.instance,
module: parsed.module,
config,
dependencies: [],
filename: 'base.svelte',
}

// @ts-ignore
// run the source through the processor
await fragmentProcessor(config, doc)

// invoke the test
return doc
}
40 changes: 2 additions & 38 deletions packages/houdini-preprocess/src/transforms/mutation.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// external imports
import * as svelte from 'svelte/compiler'
// local imports
import mutationProcessor from './mutation'
import { testConfig } from 'houdini-common'
import '../../../../jest.setup'
import { preprocessorTest } from '../utils'

describe('mutation preprocessor', function () {
test('happy path', async function () {
Expand All @@ -22,7 +19,7 @@ describe('mutation preprocessor', function () {
`)

// make sure we added the right stuff
expect(doc.instance.content).toMatchInlineSnapshot(`
expect(doc.instance?.content).toMatchInlineSnapshot(`
import _AddUserArtifact from "$houdini/artifacts/AddUser";
import { mutation } from "$houdini";

Expand All @@ -33,36 +30,3 @@ describe('mutation preprocessor', function () {
`)
})
})

async function preprocessorTest(content: string) {
const schema = `
type User {
id: ID!
}

type Mutation {
addUser: User!
}
`

// parse the document
const parsed = svelte.parse(content)

// build up the document we'll pass to the processor
const config = testConfig({ schema, verifyHash: false })

const doc = {
instance: parsed.instance,
module: parsed.module,
config,
dependencies: [],
filename: 'base.svelte',
}

// @ts-ignore
// run the source through the processor
await mutationProcessor(config, doc)

// invoke the test
return doc
}
Loading