Skip to content

Commit

Permalink
HoudiniClient Plugins (#834)
Browse files Browse the repository at this point in the history
* create e2e project

* init houdini

* use env var for token

* update lock

* update

* accept liveQuery handler as 3rd argument for now

* clean up deprecation warnings

* persist live flag in artifact

* add live to artifact type

* add liveQuery to store

* close EventSource when unsubscribing

* liveQueryHandler takes an updater instead of just returning a raw value

* use third party apply_patch for grafbase test

* pass session to LiveQueryHandler

* remove logs

* handle errors

* start document observer

* failing test and start store implementation

* fixed all tests except lists

* all tests pass

* map subs to spec in one place

* update snapshots

* remove live from artifact

* another failing test

* first pass at middleware iterator

* remove unused import

* temporary names for phases

* fix error when invoking first terminate

* tweak docs

* exception handling

* cleanup phase

* tweak

* move client logic to special directory

* add skeleton for rest of middlewares

* fetch gives full payload

* rename file

* rename document observer stages ; track loading state

* naming tweaks

* naming and comments

* plugins can specify client plugins

* passing null to client_plugins prevents function invocation

* accept url and fetchParams functions

* users can't provide plugins and pipelines together

* rework fetch plugin

* start implementating plugins

* implement input and mutation plugin ; fix fetching state tracking

* update tests

* random tidyness

* failing test

* query plugin includes last used variables in outbound requests

* update variables before subscribing in query

* move variable tracking into document observer

* marshalVariables and variablesChanged from handlers

* can check if variables changed right after assigning it

* mutation clears layer if there is a network error

* quietErrors happens at the network level now

* first pass at subscriptions

* typos

* tests pass

* tidy up type names

* strengthen test

* clean up typedefs

* improved idempotency

* rename plugin.error to plugin.throw

* query plugin uses marshaled variables

* start implementation query store

* implement query, subscription, and mutation

* query pagination

* unused imports

* make import logic synchronous

* everything builds!!

* fix import tests

* move injected plugins into plugins directory

* use getCurrentClient in houdini-react

* fix imports

* subscribe on the way out

* better hook wrapping

* update snapshot

* test cache policy

* add loading state checks to tests

* dont specify pipeline on client

* remove App.Sessions

* query pagination works!

* fix component queries

* more tests passing

* fix fragment cursor pagination

* fragment offset and metadata tests

* mutation initial state

* more tests

* fixed more tests

* remove logging

* ⚡ UPDATE: App Metadata & Session

* 🚀 UPDATE: libReporter config

* update snapshots

* move client retrieval into houdini-svelte

* plugin client_plugins can be specified as a function of config file and plugin config

* add throwOnError config

* can update store state from handle

* setup test

* flatten client plugin hook names

* update init and release notes

* update documentPlugin wrapper for new keywords

* update release notes and subscription setup

* remove index from doc frontmatter

* add doc page for client

* flesh out client docs more

* @manual_load directive & fetching default. (#831)

* ✨ NEW: info in artifact

* ✨ NEW: default init value

* 🐛 FIX: the test to be coherent!

* ✏️ UPDATE: changeset

* 🐛 FIX: lint

* ⚡ IMPROVE: serializeValue to have keys quoted

* ✨ NEW: artifact.pluginsData

* 🚧 UPDATE: all artifacts + check 1 test + docs to doc

* 🐛 FIX: defautl default is false

* 👌 FIX: lint

* 🚸 UPDATE: having the right default for core and the right tests for core

* 💡 IMPROVE: with libReporter (to comment if any config change)

* 👌 FIX: lint

* 👌 FIX: linting

* start on client plugin section

* flesh out plugin overview

* start on your first plugin

* add second plugin example

* add type definition to plugin docs

* add enter and exit phase sections

* more doc updates

* pull out data sources section

* reorder subsections

* add beforeNetwork phase

* tweak sizing and spacing of diagram

* note change of quietQueryErrors in release notes

* add $houdini/plugins export

* add section on Choosing a Phase

* update example

* subscription handler is now a function of the ClientPlugin context

* add changeset

* remove unused import

* linter

* more type imports

* make sure that the plugin root directory exists

* fix graphql return type generator

* enter prerelease mode

* start from 1.0.0-next.1 to cover last mistake

* 🎨 UPDATE: zoomable component (first use: Workflow)

* ✏️ FIX: link & mini typo

* 🎨 IMPROVE: colors

* feedback

* remove grafbase e2e test

* move pipeline config to deep dive

* comment tweak

* grammar

* add links to source of truth for typedefs

* start on fetchParams specifying body

* fetchParams can specify body

* update automatic persisted queries

* better comments

* document App.Stuff

* tweak comment

* detect spread values

* rename file

* remove unused config value

* add fragments to home page

* remove test

* rename DocumentObserver to DocumentStore

* better subscription auth logic

* client-plugin review

* add typedefs for throwOnError and fetchParams

* tweak words

* punctuation

* fix build errors

* start single step document store

* forgot to commit something

* fix document store

* add test for error throw

* remove log

* consolidate error handling

* update docs

* remove log

* fix build errors

* choosing a phase after enter vs exit

* wether -> whether

* punctuation

* small tweaks

* more tweaks

* add caption

* one word

Co-authored-by: jycouet <[email protected]>
  • Loading branch information
AlecAivazis and jycouet authored Jan 21, 2023
1 parent 44da563 commit 5298025
Show file tree
Hide file tree
Showing 242 changed files with 9,834 additions and 4,694 deletions.
6 changes: 6 additions & 0 deletions .changeset/beige-cheetahs-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'houdini': major
'houdini-svelte': major
---

Implemented new ClientPlugin architecture for HoudiniClient
5 changes: 5 additions & 0 deletions .changeset/clean-rules-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini': patch
---

in queries when we have manual_load directive fetching is false by default otherwise true
17 changes: 17 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"mode": "pre",
"tag": "next",
"initialVersions": {
"e2e-api": "1.0.0",
"e2e-next": "0.1.0",
"sveltekit": "0.0.1",
"example-kit": "0.13.0",
"scripts": "1.0.0",
"houdini": "0.20.2",
"houdini-plugin-svelte-global-stores": "0.20.2",
"houdini-react": "0.20.2",
"houdini-svelte": "0.20.2",
"site": "0.0.1"
},
"changesets": []
}
7 changes: 7 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ module.exports = {
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/prefer-as-const': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'separate-type-imports',
},
],
'unicorn/no-lonely-if': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-instanceof-array': 'off',
Expand Down
60 changes: 25 additions & 35 deletions e2e/sveltekit/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,30 @@
import type { RequestHandler } from '$houdini';
import { HoudiniClient } from '$houdini';
import { HoudiniClient, type ClientPlugin } from '$houdini';
import { error } from '@sveltejs/kit';

// For Query & Mutation
const requestHandler: RequestHandler = async ({
fetch,
text = '',
variables = {},
metadata,
session
}) => {
// Prepare the request
const url = 'http://localhost:4000/graphql';
// in order to verify that we send metadata, we need something that will log the metadata after
const logMetadata: ClientPlugin = () => ({
end(ctx, { resolve, value }) {
if (ctx.metadata?.logResult === true) {
console.info(JSON.stringify(value));
}

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

// return the result as a JSON object to Houdini
const json = await result.json();

// metadata usage example
if (metadata?.logResult === true) {
console.info(JSON.stringify(json));
resolve(ctx);
}

return json;
};
});

// Export the Houdini client
export default new HoudiniClient(requestHandler);
export default new HoudiniClient({
url: 'http://localhost:4000/graphql',
fetchParams({ session }) {
return {
headers: {
Authorization: `Bearer ${session?.user?.token}` // session usage example
}
};
},
throwOnError: {
operations: ['all'],
error: (errors) => error(500, errors.map((error) => error.message).join('. ') + '.')
},
plugins: [logMetadata]
});
2 changes: 1 addition & 1 deletion e2e/sveltekit/src/lib/QueryComponent.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { graphql, FragmentQueryVarsStore } from '$houdini';
import { graphql } from '$houdini';
export function _FragmentQueryVarsVariables({ props }: { props: { id?: string } }) {
return {
Expand Down
3 changes: 1 addition & 2 deletions e2e/sveltekit/src/routes/fetching/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ test.describe('fetching', () => {
await goto(page, routes.Home);

// Switch page and check the first console log
// It's expected to stay true until the first fetch!
const [msg] = await Promise.all([
waitForConsoleInfo(page),
clientSideNavigation(page, routes.fetching_without_load)
]);
expect(msg.text()).toBe('without_load - fetching: true');
expect(msg.text()).toBe('without_load - fetching: false');

const [msg2] = await Promise.all([
waitForConsoleInfo(page),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
`);
const fragmentResult = paginatedFragment(
$: fragmentResult = paginatedFragment(
$queryResult.data?.user ?? null,
graphql(`
fragment OffsetFragment on User {
Expand Down
6 changes: 4 additions & 2 deletions e2e/sveltekit/src/routes/stores/metadata/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ test.describe('Metadata Page', () => {
// Go on the page
await goto_expect_n_gql(page, routes.Stores_Metadata, 1);

expect(displayed).toBe(`{"data":{"session":"1234-Houdini-Token-5678"}}`);
expect(displayed).toBe(
'{"fetching":false,"variables":{},"data":{"session":"1234-Houdini-Token-5678"},"errors":null,"partial":false,"source":"network"}'
);

//Click the button
// Mutate the data (that will be displayed in the console)
await expect_1_gql(page, 'button[id=mutate]');

expect(displayed).toBe(
`{"data":{"updateUser":{"id":"list-store-user-subunsub:5","name":"Hello!"}}}`
'{"fetching":false,"variables":{"id":"5","name":"Hello!"},"data":{"updateUser":{"id":"list-store-user-subunsub:5","name":"Hello!"}},"errors":null,"partial":false,"source":"network"}'
);
});
});
5 changes: 3 additions & 2 deletions e2e/sveltekit/src/routes/stores/mutation/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ test.describe('Mutation Page', () => {
data: null,
errors: null,
fetching: false,
isOptimisticResponse: false,
variables: null
variables: null,
partial: false,
source: null
};
await expectToBe(page, stry(defaultStoreValues) ?? '', '[id="store-value"]');
});
Expand Down
2 changes: 1 addition & 1 deletion e2e/sveltekit/src/routes/stores/prefetch-[userId]/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test.describe('prefetch-[userId] Page', () => {
await goto(page, routes.Stores_Prefetch_UserId_2);

const dataDisplayedSSR =
'{"data":{"user":{"id":"store-user-query:2","name":"Samuel Jackson"}},"errors":null,"fetching":false,"partial":false,"source":"ssr","variables":{"id":"2"}}';
'{"data":{"user":{"id":"store-user-query:2","name":"Samuel Jackson"}},"errors":null,"fetching":false,"partial":false,"source":"network","variables":{"id":"2"}}';

// The page should have the right data directly
await expectToBe(page, dataDisplayedSSR);
Expand Down
17 changes: 12 additions & 5 deletions e2e/sveltekit/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,26 @@ const config = {

// This plugin is checking build sizes by lib.
// It's not required for Houdini to work.
// If there is a config change needed, please comment it and let us know.
libReporter([
{
name: 'houdini',
includes: ['$houdini/runtime', 'houdini.config.js'],
excludes: ['vite/preload-helper']
name: 'houdini runtime core',
includes: ['$houdini/runtime', 'src/client.ts'],
excludes: [
'vite/preload-helper',
'$houdini/index.js',
'houdini.config.js',
'src/client.ts',
'sveltejs'
]
},
{
name: 'houdini-svelte',
name: 'houdini runtime svelte',
includes: ['$houdini/plugins/houdini-svelte/runtime', 'src/client.ts'],
excludes: ['vite/preload-helper', '$houdini/runtime', '$houdini/index.js', 'svelte']
},
{
name: 'houdini-full-e2e',
name: 'houdini full e2e',
includes: ['$houdini', 'src/client.ts', 'houdini.config.js'],
excludes: ['vite/preload-helper', 'svelte']
}
Expand Down
40 changes: 11 additions & 29 deletions example/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
import { browser } from '$app/environment'
import type { RequestHandlerArgs, SubscriptionHandler } from '$houdini'
import { HoudiniClient } from '$houdini'
import { subscriptionPlugin } from '$houdini/plugins'
import { createClient as createWSClient } from 'graphql-ws'

// For Query & Mutation
async function fetchQuery({ fetch, text = '', variables = {} }: RequestHandlerArgs) {
const result = await fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: text,
variables,
}),
})

return await result.json()
}

// For subscription (client only)
let socketClient: SubscriptionHandler | null = null
if (browser) {
// @ts-ignore
socketClient = createWSClient({
url: 'ws://localhost:4000/graphql',
})
}

// Export the Houdini client
export default new HoudiniClient(fetchQuery, socketClient)
export default new HoudiniClient({
url: 'http://localhost:4000/graphql',
plugins: [
subscriptionPlugin(() =>
createWSClient({
url: 'ws://localhost:4000/graphql',
})
),
],
})
2 changes: 2 additions & 0 deletions houdini.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { HoudiniClient } from './packages/houdini/src/runtime/client'

declare namespace App {
interface Session {}
interface Metadata {}
Expand Down
2 changes: 1 addition & 1 deletion packages/_scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ async function build({ package_json, source, bundle = true, plugin, cmd }) {
bundle,
platform: 'node',
format: which,
external: bundle ? ['vite', 'HOUDINI_CONFIG_PATH', 'HOUDINI_CLIENT_PATH'] : [],
external: bundle ? ['vite'] : [],
banner: {
js: header,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/houdini-plugin-svelte-global-stores/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "houdini-plugin-svelte-global-stores",
"version": "0.20.2",
"version": "1.0.0-next.1",
"description": "The svelte global store plugin for houdini",
"keywords": [
"typescript",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Config, fs, GenerateHookInput } from 'houdini'
import type { Config, GenerateHookInput } from 'houdini'
import { fs } from 'houdini'

import { global_stores_directory } from '../kit'
import stores from './stores'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CollectedGraphQLDocument, fs, GenerateHookInput, path } from 'houdini'
import type { CollectedGraphQLDocument, GenerateHookInput } from 'houdini'
import { fs, path } from 'houdini'

import { store_name, stores_directory_name } from '../../../../../houdini-svelte/src/plugin/kit'
import { global_store_name, global_stores_directory } from '../../kit'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cleanupFiles, fs, ArtifactKind, GenerateHookInput, path } from 'houdini'
import type { GenerateHookInput } from 'houdini'
import { cleanupFiles, fs, ArtifactKind, path } from 'houdini'

import { global_stores_directory } from '../../kit'
import { fragmentStore } from './fragment'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CollectedGraphQLDocument, fs, GenerateHookInput, path } from 'houdini'
import type { CollectedGraphQLDocument, GenerateHookInput } from 'houdini'
import { fs, path } from 'houdini'

import { store_name, stores_directory_name } from '../../../../../houdini-svelte/src/plugin/kit'
import { global_stores_directory, global_store_name } from '../../kit'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CollectedGraphQLDocument, fs, GenerateHookInput, path } from 'houdini'
import type { CollectedGraphQLDocument, GenerateHookInput } from 'houdini'
import { fs, path } from 'houdini'

import { store_name, stores_directory_name } from '../../../../../houdini-svelte/src/plugin/kit'
import { global_stores_directory, global_store_name } from '../../kit'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CollectedGraphQLDocument, fs, GenerateHookInput, path } from 'houdini'
import type { CollectedGraphQLDocument, GenerateHookInput } from 'houdini'
import { fs, path } from 'houdini'

import { stores_directory_name, store_name } from '../../../../../houdini-svelte/src/plugin/kit'
import { global_stores_directory, global_store_name } from '../../kit'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HoudiniError, path, PluginFactory } from 'houdini'
import type { PluginFactory } from 'houdini'
import { HoudiniError, path } from 'houdini'

import { store_name } from '../../../houdini-svelte/src/plugin/kit'
import generate from './codegen'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Config, path } from 'houdini'
import type { Config } from 'houdini'
import { path } from 'houdini'

import { HoudiniPluginSvelteGlobalStoresConfig } from '.'
import type { HoudiniPluginSvelteGlobalStoresConfig } from '.'

export function global_stores_directory_name() {
return 'stores'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CollectedGraphQLDocument, Config, ConfigFile } from 'houdini'
import type { CollectedGraphQLDocument, Config, ConfigFile } from 'houdini'
import { runPipeline } from 'houdini/codegen'
import { mockCollectedDoc, testConfig } from 'houdini/test'

Expand Down
2 changes: 1 addition & 1 deletion packages/houdini-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "houdini-react",
"version": "0.20.2",
"version": "1.0.0-next.1",
"private": true,
"description": "The React plugin for houdini",
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions packages/houdini-react/src/plugin/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Config } from 'houdini'
import type { Config } from 'houdini'

import { HoudiniReactPluginConfig } from '.'
import type { HoudiniReactPluginConfig } from '.'

export function plugin_config(config: Config): Required<HoudiniReactPluginConfig> {
return config.pluginConfig<HoudiniReactPluginConfig>('houdini-react')
Expand Down
2 changes: 1 addition & 1 deletion packages/houdini-react/src/plugin/extract.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parse } from '@babel/parser'
import { Config } from 'houdini'
import type { Config } from 'houdini'
import * as recast from 'recast'

export function extract_documents(config: Config, filepath: string, content: string) {
Expand Down
Loading

1 comment on commit 5298025

@vercel
Copy link

@vercel vercel bot commented on 5298025 Jan 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.