Skip to content

Commit

Permalink
evaluate the runtime scalars in query plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
AlecAivazis committed Mar 6, 2024
1 parent 19bb764 commit 31e3c49
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 13 deletions.
11 changes: 8 additions & 3 deletions packages/houdini-svelte/src/plugin/transforms/kit/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,12 +528,17 @@ function variable_function_for_query(
for (const definition of query.variableDefinitions ?? []) {
const unwrapped = unwrapType(page.config, definition.type)

// if the type is a runtime scalar, its optional
const runtime_scalar =
page.config.configFile.features?.runtimeScalars?.[unwrapped.type.name]

// we need to remember the definition if
// the argument to the operation is non-null
// the url param doesn't exist or does exist but is optional
if (
unwrapped.wrappers[unwrapped.wrappers.length - 1] === TypeWrapper.NonNull &&
!definition.defaultValue &&
!runtime_scalar &&
(!params[definition.variable.name.value] ||
params[definition.variable.name.value].optional)
) {
Expand Down Expand Up @@ -564,8 +569,8 @@ function variable_function_for_query(
AST.variableDeclarator(
AST.identifier('result'),
AST.objectExpression(
Object.entries(has_args).map(([arg, type]) =>
AST.objectProperty(
Object.entries(has_args).map(([arg, type]) => {
return AST.objectProperty(
AST.identifier(arg),
AST.callExpression(AST.identifier('parseScalar'), [
AST.identifier('config'),
Expand All @@ -579,7 +584,7 @@ function variable_function_for_query(
),
])
)
)
})
)
),
]),
Expand Down
10 changes: 6 additions & 4 deletions packages/houdini/src/runtime/client/plugins/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { beforeEach, expect, test, vi } from 'vitest'
import { createPluginHooks, HoudiniClient, type HoudiniClientConstructorArgs } from '..'
import { testConfigFile } from '../../../test'
import { Cache } from '../../cache/cache'
import { CachePolicy, PendingValue } from '../../lib'
import { CachePolicy, PendingValue, QueryArtifact } from '../../lib'
import { setMockConfig } from '../../lib/config'
import { ArtifactKind, DataSource } from '../../lib/types'
import type { ClientPlugin } from '../documentStore'
Expand Down Expand Up @@ -476,7 +476,7 @@ test('loading states when fetching is true', async function () {
* Utilities for testing the cache plugin
*/
export function createStore(
args: Partial<HoudiniClientConstructorArgs> = {}
args: Partial<HoudiniClientConstructorArgs> & { artifact?: QueryArtifact } = {}
): DocumentStore<any, any> {
// if we dont have anything passed, just use the fake fetch as the plugin
if (!args.plugins && !args.pipeline) {
Expand All @@ -493,7 +493,7 @@ export function createStore(
plugins: args.plugins ? createPluginHooks(client.plugins) : undefined,
pipeline: args.pipeline ? createPluginHooks(client.plugins) : undefined,
client,
artifact: {
artifact: args.artifact ?? {
kind: ArtifactKind.Query,
hash: '7777',
raw: 'RAW_TEXT',
Expand Down Expand Up @@ -535,7 +535,7 @@ export function createStore(
})
}

function fakeFetch({
export function fakeFetch({
result = {
data: {
viewer: {
Expand All @@ -551,9 +551,11 @@ function fakeFetch({
partial: false,
stale: false,
},
spy = vi.fn(),
} = {}) {
return (() => ({
network(ctx, { resolve }) {
spy(ctx)
resolve(ctx, { ...result })
},
})) as ClientPlugin
Expand Down
76 changes: 76 additions & 0 deletions packages/houdini/src/runtime/client/plugins/query.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { beforeEach, expect, test, vi } from 'vitest'

import { testConfigFile } from '../../../test'
import { setMockConfig } from '../../lib/config'
import { createStore, fakeFetch } from './cache.test'
import { query } from './query'

const config = testConfigFile()
beforeEach(async () => {
setMockConfig(config)
})

test('query plugin evaluates runtime scalars', async function () {
const fetchSpy = vi.fn()

const store = createStore({
artifact: {
kind: 'HoudiniQuery',
hash: '7777',
raw: 'RAW_TEXT',
name: 'TestArtifact',
rootType: 'Query',
pluginData: {},
enableLoadingState: 'local',
input: {
fields: {
id: 'ID',
},
types: {},
defaults: {},
runtimeScalars: {
id: 'ViewerIDFromSession',
},
},
selection: {
fields: {
viewer: {
type: 'User',
visible: true,
keyRaw: 'viewer',
loading: { kind: 'continue' },
selection: {
fields: {
id: {
type: 'ID',
visible: true,
keyRaw: 'id',
},
firstName: {
type: 'String',
visible: true,
keyRaw: 'firstName',
loading: { kind: 'value' },
},
__typename: {
type: 'String',
visible: true,
keyRaw: '__typename',
},
},
},
},
},
},
},
pipeline: [query, fakeFetch({ spy: fetchSpy })],
})

// run the query with an artifact that contains runtime scalars
await store.send({ session: { token: 'world' } })

// the fetch spy should
const ctx = fetchSpy.mock.calls[0][0]

expect(ctx.variables).toEqual({ id: 'world' })
})
21 changes: 21 additions & 0 deletions packages/houdini/src/runtime/client/plugins/query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cache from '../../cache'
import { type RuntimeScalarPayload } from '../../lib'
import { type SubscriptionSpec, ArtifactKind, DataSource } from '../../lib/types'
import type { ClientPlugin } from '../documentStore'
import { documentPlugin } from '../utils'
Expand All @@ -13,9 +14,29 @@ export const query: ClientPlugin = documentPlugin(ArtifactKind.Query, function (
// the function to call when a query is sent
return {
start(ctx, { next }) {
const runtimeScalarPayload: RuntimeScalarPayload = {
session: ctx.session,
}

// make sure to include the last variables as well as the new ones
ctx.variables = {
...lastVariables,
// we need to evaluate any runtime scalars but allow the user to overwrite them
// by explicitly passing variables
...Object.fromEntries(
Object.entries(ctx.artifact.input?.runtimeScalars ?? {}).map(
([field, type]) => {
const runtimeScalar = ctx.config.features?.runtimeScalars?.[type]
// make typescript happy
if (!runtimeScalar) {
return [field, type]
}

// resolve the runtime scalar
return [field, runtimeScalar.resolve(runtimeScalarPayload)]
}
)
),
...ctx.variables,
}
next(ctx)
Expand Down
11 changes: 7 additions & 4 deletions packages/houdini/src/runtime/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,21 +225,24 @@ export type ConfigFile = {
features?: {
/** Interact with the cache directly using an imperative API.*/
imperativeCache?: boolean
/** [React Only] Emebed component references in query responses*/
componentFields?: boolean
/** [React Only] Compute query variable values using a runtime scalar*/
runtimeScalars?: Record<
string,
{
// the equivalent GraphQL type
type: string
// the function to call that serializes the type for the API
resolve: (args: { session: App.Session }) => any
resolve: (args: RuntimeScalarPayload) => any
}
>
/** [React Only] Emebed component references in query responses*/
componentFields?: boolean
}
}

export type RuntimeScalarPayload = {
session?: App.Session | null | undefined
}

type RouterConfig = {
auth?: AuthStrategy
apiEndpoint?: string
Expand Down
2 changes: 2 additions & 0 deletions packages/houdini/src/runtime/lib/scalars.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ const artifact: QueryArtifact = {
enumValue: 'EnumValue',
},
},
runtimeScalars: {},
defaults: {},
},
}

Expand Down
4 changes: 2 additions & 2 deletions packages/houdini/src/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as graphql from 'graphql'
import { vol } from 'memfs'

import session from '../../../houdini-svelte/src/plugin/transforms/kit/session'
import { runPipeline } from '../codegen'
import type { Document } from '../lib'
import { Config, fs, path } from '../lib'
Expand Down Expand Up @@ -298,7 +297,8 @@ export function testConfigFile({ plugins, ...config }: Partial<ConfigFile> = {})
runtimeScalars: {
ViewerIDFromSession: {
type: 'ID',
resolve: ({ session }) => session,
resolve: ({ session }: { session?: App.Session | null | undefined }) =>
(session as unknown as any).token,
},
},
},
Expand Down

0 comments on commit 31e3c49

Please sign in to comment.