Skip to content

Commit

Permalink
Add sessions (#508)
Browse files Browse the repository at this point in the history
* Merge fehnomenal's work (#507)

* Allow passing session data to the fetch function

* Prefer the client side session

* Receive the server session only in the browser

* Separate client and server sessions more clearly

* make test more resilient

* fake root layout file

* fake +layout.server.js

* kit transform threads session from +layout.server.js to client

* remove client.init

Co-authored-by: Andreas Fehn <[email protected]>

* rename setServerSession to setSession

* receiving server session data needs to be reactive

* document no more init

* update authentication guide

* auth guide tweaks

* use App.Session for SessionData

* remove document proxy

* added integration test for session

* make checker happy

* add changeset

* remove unused import

* fix inconsistent absolutely path in CI

* tweak util names

* find_exported_fn considers export const

* better support for load functions with implicit returns

* track hydrated state in root layout

* pass session to client side operations (mutations, loadNextPage, etc)

* updated snapshots

* add log if no session in locals

* block on error

* document onError blocking

* update snapshot

* fix duplicate test name

Co-authored-by: Andreas Fehn <[email protected]>
  • Loading branch information
AlecAivazis and fehnomenal authored Sep 4, 2022
1 parent 4983a76 commit 60ecb33
Show file tree
Hide file tree
Showing 58 changed files with 1,075 additions and 530 deletions.
5 changes: 5 additions & 0 deletions .changeset/nervous-pots-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini': patch
---

added support for sessions
4 changes: 3 additions & 1 deletion integration/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ declare namespace App {
// interface Platform {}

interface Session {
token?: string | null;
user?: {
token: string;
};
}

// interface Stuff {}
Expand Down
15 changes: 10 additions & 5 deletions integration/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export function getSession() {
return {
token: '1234-Houdini-Token-5678'
};
}
import houdini from './lib/graphql/houdiniClient';
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
// set the session information for this event
houdini.setSession(event, { user: { token: '1234-Houdini-Token-5678' } });

// pass the event onto the default handle
return await resolve(event);
};
12 changes: 9 additions & 3 deletions integration/src/lib/graphql/houdiniClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import { HoudiniClient } from '$houdini';
import { stry } from '@kitql/helper';

// For Query & Mutation
async function fetchQuery({ fetch, text = '', variables = {}, metadata }: RequestHandlerArgs) {
async function fetchQuery({
fetch,
text = '',
variables = {},
metadata,
session
}: RequestHandlerArgs) {
// Prepare the request
const url = import.meta.env.VITE_GRAPHQL_ENDPOINT || 'http://localhost:4000/graphql';

// regular fetch (Server & Client)
const result = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
// Authorization: `Bearer ${session?.token}` // session usage example
'Content-Type': 'application/json',
Authorization: `Bearer ${session?.user?.token}` // session usage example
},
body: JSON.stringify({
query: text,
Expand Down
3 changes: 0 additions & 3 deletions integration/src/lib/graphql/operations/QUERY.Session.gql

This file was deleted.

1 change: 1 addition & 0 deletions integration/src/lib/utils/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const routes = {
Stores_Metadata: '/stores/metadata',
Stores_Endpoint_Query: '/stores/endpoint-query',
Stores_Endpoint_Mutation: '/stores/endpoint-mutation',
Stores_Session: '/stores/session',

Stores_Partial_List: '/stores/partial/partial_List',
Stores_Pagination_query_forward_cursor: '/stores/pagination/query/forward-cursor',
Expand Down
3 changes: 1 addition & 2 deletions integration/src/routes/plugin/query/onError/+page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { graphql } from '$houdini';
import type { OnErrorEvent } from './$houdini';

export const houdini_load = graphql`
query PreprocessorOnErrorTestQuery {
Expand All @@ -9,7 +8,7 @@ export const houdini_load = graphql`
}
`;

export const onError = ({ error, input }: OnErrorEvent) => {
export const onError = () => {
return {
fancyMessage: 'hello'
};
Expand Down
9 changes: 8 additions & 1 deletion integration/src/routes/plugin/query/onError/spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { test } from '@playwright/test';
import { routes } from '../../../../lib/utils/routes.js';
import { expectToBe, goto } from '../../../../lib/utils/testsHelper.js';
import { clientSideNavigation, expectToBe, goto } from '../../../../lib/utils/testsHelper.js';

test.describe('query preprocessor', () => {
test('onError hook', async ({ page }) => {
await goto(page, routes.Plugin_query_onError);

await expectToBe(page, 'hello');
});

test('onError hook blocks on client', async ({ page }) => {
await goto(page, routes.Home);
await clientSideNavigation(page, routes.Plugin_query_onError);

await expectToBe(page, 'hello');
});
});
3 changes: 1 addition & 2 deletions integration/src/routes/plugin/query/scalars/spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { routes } from '../../../../lib/utils/routes.js';
import {
expect_1_gql,
expectToBe,
goto,
navSelector,
clientSideNavigation,
expect_0_gql
} from '../../../../lib/utils/testsHelper.js';
import { expect, test } from '@playwright/test';
import { test } from '@playwright/test';

test.describe('query preprocessor variables', () => {
test('query values get unmarshaled into complex values', async function ({ page }) {
Expand Down
2 changes: 1 addition & 1 deletion integration/src/routes/stores/endpoint-query/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
</script>

<div id="result">
{JSON.stringify({ data })}
{data.hello}
</div>
2 changes: 1 addition & 1 deletion integration/src/routes/stores/endpoint-query/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ test.describe('query endpoint', () => {
test('happy path query ', async ({ page }) => {
await goto(page, routes.Stores_Endpoint_Query);

await expectToBe(page, JSON.stringify({ data: { hello: 'Hello World! // From Houdini!' } }));
await expectToBe(page, 'Hello World! // From Houdini!');
});
});
15 changes: 15 additions & 0 deletions integration/src/routes/stores/session/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { graphql } from '$houdini';
const Session = graphql`
query Session {
session
}
`;
</script>

<h1>SSR Session</h1>

<div id="result">
{$Session.data?.session}
</div>
13 changes: 13 additions & 0 deletions integration/src/routes/stores/session/spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { test } from '@playwright/test';
import { routes } from '../../../lib/utils/routes.js';
import { expectToBe, expect_0_gql } from '../../../lib/utils/testsHelper.js';

test.describe('SSR Session Page', () => {
test('No GraphQL request & Should display the session token', async ({ page }) => {
await page.goto(routes.Stores_Session);

await expect_0_gql(page);

await expectToBe(page, '1234-Houdini-Token-5678');
});
});
5 changes: 5 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import fs from 'node:fs'
import typescript from 'rollup-plugin-typescript2'

import changesetConfig from './.changeset/config.json'
import packgeJSON from './package.json'

// grab the environment variables
Expand Down Expand Up @@ -43,6 +45,9 @@ export default {
nodeResolve({ preferBuiltins: true }),
replace({
HOUDINI_VERSION: packgeJSON.version,
SITE_URL: fs.existsSync('./.changeset/pre.json')
? 'https://docs-next-kohl.vercel.app'
: 'https://houdinigraphql.com',
}),
],
}
9 changes: 6 additions & 3 deletions site/src/routes/api/routes.svx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Queries in Houdini

<script>
import { DeepDive, Warning, Transformation, Highlight } from '~/components'

const forwardPaginationStoreBefore = `
query MyFriends {
viewer {
Expand Down Expand Up @@ -241,9 +241,12 @@ export function afterLoad({ data }) {

#### `onError`

If defined, the load function will invoked this function instead of throwing an error when an error is received.
If defined, the load function will invoked this function instead of throwing an error when an error is received.
It receives three inputs: the load event, the inputs for each query, and the error that was encountered. Just like
the other hooks, `onError` can return an object that provides props to the route.
the other hooks, `onError` can return an object that provides props to the route. If you do define this hook, Houdini
will have to block requests to the route in order to wait for the result. For more information about
blocking during navigation, check out [this section](/api/query/store#server-side-blocking) of the query store
docs.

```javascript
// src/routes/myRoute/+page.js
Expand Down
59 changes: 29 additions & 30 deletions site/src/routes/guides/authentication.svx
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,53 @@ description: A guide to authentication in Houdini

# Authentication

houdini defers to SvelteKit's sessions and/or metadata for authentication. Assuming that these args has been populated somehow, you can access it through arguments in the `fetchQuery` definition:
Houdini's support for user sessions comes in 2 parts. First, you need to add a `hook.js` file that defines the
session for that user:

```typescript
// client.ts
/// src/hooks.js

import houdiniClient from './lib/client'

export async function handle({ event, resolve }) {
// get the user information however you want
const user = await authenticateUser(event)

// set the session information for this event
houdiniClient.setSession(event, { user })

// pass the event onto the default handle
return await resolve(event)
}
```

The only thing that's left is to use the `session` parameter passed to your client's network function to access the information:

```typescript
/// src/lib/client.ts

import type { RequestHandlerArgs } from '$houdini'
import { HoudiniClient } from '$houdini'

// For Query & Mutation
async function fetchQuery({
fetch,
text = '',
variables = {},
session,
metadata
}: RequestHandlerArgs) {
// Prepare the request
const url = import.meta.env.VITE_GRAPHQL_ENDPOINT || 'http://localhost:4000/api/graphql'

// regular fetch (Server & Client)
const result = await fetch(url, {
async function fetchQuery({ fetch, text, variables = {}, session }) {
const result = await fetch(import.meta.env.VITE_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${session?.token}` // session usage example
Authorization: `Bearer ${session?.user.token}`
},
body: JSON.stringify({
query: text,
variables
})
})

// extract and assign the json body of the response to a variable
const json = await result.json()

// metadata usage example
if (metadata?.logResult === true) {
console.info(JSON.stringify(json, null, 2))
}

return json
return await result.json()
}

// Export the Houdini client
export const houdiniClient = new HoudiniClient(fetchQuery)
export default new HoudiniClient(fetchQuery)
```

You can also type your Session & Metadata as follow in `src/app.d.ts`:
Remember, you can define types for your Session & Metadata by adding this to `src/app.d.ts`:

```typescript
/// <reference types="@sveltejs/kit" />
Expand All @@ -63,7 +62,7 @@ declare namespace App {
}

interface Metadata {
logResult?: boolean | null
abortController?: AbortController
}
}
```
10 changes: 10 additions & 0 deletions site/src/routes/guides/migrating-to-016.svx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ Also, while you're here, you can delete that ugly `server.fs.allow` config you h

Remove the old preprocessor but leave the alias config.

### `src/routes/+layout.svelte`

```diff
- import client from '../houdiniClient'
-
- client.init()
```

You no longer need to call `client.init`

## 2. Inline Documents

Apart from `fragment` and `paginatedFragment`, all functions that used to wrap
Expand Down
14 changes: 2 additions & 12 deletions site/src/routes/guides/setting-up-your-project.svx
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,6 @@ export default {
}
```

And finally, we need to configure our application to use the generated network layer. To do
this, add the following block of code to `src/routes/+layout.svelte` and any named layouts.

```html
<script>
import houdiniClient from '../houdiniClient'

houdiniClient.init()
</script>
```

### Svelte

If you are building a vanilla svelte project, you will have to configure the compiler and preprocessor to generate the correct logic by setting the `framework`
Expand All @@ -112,7 +101,8 @@ export default defineConfig({
```

If you aren't using vite, it's a lot harder to give an exact recommendation but somehow
you should import houdini's preprocessor and pass it to your svelte config
you should import houdini's preprocessor and pass it to your svelte config. You will also
need to make sure that the `$houdini` alias resolves to the directory in the root of your project.

```javascript
// svelte.config.js
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
parseJS,
HoudiniError,
} from '../common'
import { getSiteUrl } from '../runtime/lib/constants'
import { siteURL } from '../runtime/lib/constants'
import * as generators from './generators'
import * as transforms from './transforms'
import { CollectedGraphQLDocument, ArtifactKind } from './types'
Expand Down Expand Up @@ -160,7 +160,7 @@ export async function runPipeline(config: Config, docs: CollectedGraphQLDocument
const major = parseInt(previousVersion.split('.')[1])
if (major < 16) {
console.log(
`❓ For a description of what's changed, visit this guide: ${getSiteUrl()}/guides/release-notes`
`❓ For a description of what's changed, visit this guide: ${siteURL}/guides/release-notes`
)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/cmd/generators/definitions/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test('adds internal documents to schema', async function () {
"""
@paginate is used to to mark a field for pagination.
More info in the [doc](https://docs-next-kohl.vercel.app/guides/pagination).
More info in the [doc](SITE_URL/guides/pagination).
"""
directive @paginate(name: String) on FIELD
Expand Down Expand Up @@ -96,7 +96,7 @@ test('list operations are included', async function () {
"""
@paginate is used to to mark a field for pagination.
More info in the [doc](https://docs-next-kohl.vercel.app/guides/pagination).
More info in the [doc](SITE_URL/guides/pagination).
"""
directive @paginate(name: String) on FIELD
Expand Down Expand Up @@ -176,7 +176,7 @@ test("writing twice doesn't duplicate definitions", async function () {
"""
@paginate is used to to mark a field for pagination.
More info in the [doc](https://docs-next-kohl.vercel.app/guides/pagination).
More info in the [doc](SITE_URL/guides/pagination).
"""
directive @paginate(name: String) on FIELD
Expand Down
Loading

0 comments on commit 60ecb33

Please sign in to comment.