diff --git a/src/client/browser.ts b/src/client/browser.ts new file mode 100644 index 0000000..7ee7ce8 --- /dev/null +++ b/src/client/browser.ts @@ -0,0 +1,46 @@ +import { + createClient, + dedupExchange, + cacheExchange, + fetchExchange, + ssrExchange, + type ClientOptions, + type Client, +} from "@urql/core"; + +let client: Client; +let ssr: ReturnType; + +export function getClient() { + assertConfigured(); + return client; +} + +export function configureClient(config: ClientOptions) { + ssr = ssrExchange({ isClient: true }); + const exchanges = [dedupExchange, cacheExchange, ssr, fetchExchange]; + if (process.env.NODE_ENV !== "production") { + // eslint-disable-next-line @typescript-eslint/no-var-requires + exchanges.push(require("@urql/devtools").devtoolsExchange); + } + + client = createClient({ + exchanges, + fetch, + ...config, + }); +} + +export function hydrateQuery(opKey: string, data: any, error: any) { + assertConfigured(); + ssr.restoreData({ + [opKey]: { + data: data ? JSON.stringify(data) : undefined, + error, + }, + }); +} + +function assertConfigured() { + if (!client) throw new Error(" not configured."); +} diff --git a/src/client/index.ts b/src/client/index.ts deleted file mode 100644 index 7a23c60..0000000 --- a/src/client/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - createClient as internalCreate, - dedupExchange, - cacheExchange, - fetchExchange, - ssrExchange, -} from "@urql/core"; -import lookup from "./lookup"; - -type Fetch = typeof fetch; - -export function createClient({ - isServer, - fetch, -}: { - isServer: boolean; - fetch?: Fetch; -}) { - // The `ssrExchange` must be initialized with `isClient` and `initialState` - const ssr = ssrExchange({ isClient: !isServer }); - let exchanges = [ - dedupExchange, - cacheExchange, - ssr, // Add `ssr` in front of the `fetchExchange` - fetchExchange, - ]; - - if (typeof window === "object" && process.env.NODE_ENV !== "production") { - exchanges = [ - // eslint-disable-next-line @typescript-eslint/no-var-requires - require("@urql/devtools").devtoolsExchange, - ...exchanges, - ]; - } - - const client = internalCreate({ - exchanges, - fetch, - ...lookup.config, - }); - - return { client, ssr }; -} diff --git a/src/client/node.ts b/src/client/node.ts new file mode 100644 index 0000000..8df1f80 --- /dev/null +++ b/src/client/node.ts @@ -0,0 +1,61 @@ +import fetchImpl from "make-fetch-happen"; +import { + createClient, + dedupExchange, + cacheExchange, + fetchExchange, + ssrExchange, + type ClientOptions, + type Client, +} from "@urql/core"; + +const kClient = Symbol("client"); +const strictSSL = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0"; + +export function getClient(out: any): Client { + const client = out.global[kClient]; + if (!client) throw new Error(" not configured."); + return out.global[kClient]; +} + +export function configureClient(config: ClientOptions, out: any) { + out.global[kClient] = createClient({ + exchanges: [ + dedupExchange, + cacheExchange, + ssrExchange({ isClient: false }), + fetchExchange, + ], + fetch: ((url: string, options: fetchImpl.FetchOptions) => { + const incomingMessage = + (out.stream && (out.stream.req || out.stream.request)) || + out.global.req || + out.global.request; + if (!incomingMessage) return fetchImpl(url, options); + + return fetchImpl( + new URL( + url, + `${incomingMessage.protocol}://${incomingMessage.headers.host}` + ).toString(), + { + ...options, + strictSSL, + headers: { + ...incomingMessage.headers, + ...options.headers, + }, + } + ); + }) as any, + ...config, + }); +} + +export function whenConfigured() { + throw new Error("Cannot be called on the server."); +} + +export function hydrateQuery() { + throw new Error("Cannot be called on the server."); +} diff --git a/src/client/package.json b/src/client/package.json new file mode 100644 index 0000000..838eb2c --- /dev/null +++ b/src/client/package.json @@ -0,0 +1,4 @@ +{ + "main": "./node", + "browser": "./browser" +} diff --git a/src/components/gql-client/index.marko b/src/components/gql-client/index.marko index bb74dce..493273f 100644 --- a/src/components/gql-client/index.marko +++ b/src/components/gql-client/index.marko @@ -1,7 +1,7 @@ -import lookup from "../../client/lookup"; +import { configureClient } from "../../client"; class { - onCreate(input) { - lookup.config = input; + onCreate(input, out) { + configureClient(input, out); } } diff --git a/src/components/gql-mutation/index.marko b/src/components/gql-mutation/index.marko index f87c682..f71c856 100644 --- a/src/components/gql-mutation/index.marko +++ b/src/components/gql-mutation/index.marko @@ -1,4 +1,4 @@ -import { createClient } from "../../client"; +import { getClient } from "../../client"; class { onCreate() { this.state = { results: {}, fetching: false }; @@ -8,9 +8,8 @@ $ const { renderBody, mutation, requestPolicy } = input; <${renderBody}( (variables, options) => { - const { client } = window.$$GQL || (window.$$GQL = createClient({})); component.setState("fetching", true); - return client + return getClient() .mutation(mutation, variables, { requestPolicy, ...options }) .toPromise() .then((results) => { diff --git a/src/components/gql-query/components/gql-query-client/index.marko b/src/components/gql-query/components/gql-query-client/index.marko index ef082da..c2c86d8 100644 --- a/src/components/gql-query/components/gql-query-client/index.marko +++ b/src/components/gql-query/components/gql-query-client/index.marko @@ -1,57 +1,58 @@ import { pipe, subscribe } from "wonka"; +import { getClient, hydrateQuery } from "../../../../client"; import readyLookup from "../../ready-lookup"; -static function doQuery( - component, - { query, variables, requestPolicy }, - options -) { - if (component.unsubscribe) { - component.unsubscribe(); - component.unsubscribe = null; - } - - component.state.fetching = true; - component.unsubscribe = pipe( - window.$$GQL.client.query(query, variables, { - requestPolicy, - ...options, - }), - subscribe(({ data, error }) => { - component.state.data = data; - component.state.error = error; - component.state.fetching = false; - }) - ).unsubscribe; -} class { onCreate({ data, error, opKey }) { this.state = { data, error, fetching: false }; + this.mounted = false; if (typeof document !== "undefined" && opKey) { - window.$$GQL.ssr.restoreData({ - [opKey]: { - data: data ? JSON.stringify(data) : undefined, - error, - }, - }); + hydrateQuery(opKey, data, error); readyLookup[this.id](); } } onInput(input) { - if (!input.opKey) doQuery(this, input); + if (!input.opKey) { + if (this.mounted) { + this.doQuery(); + } else { + this.once("mounted", () => this.doQuery()); + } + } + } + onMount() { + this.mounted = true; } onDestroy() { + this.stopQuery(); + } + stopQuery() { this.unsubscribe && this.unsubscribe(); + this.unsubscribe = undefined; + } + doQuery(options) { + this.stopQuery(); + this.state.fetching = true; + this.unsubscribe = pipe( + getClient().query(this.input.query, this.input.variables, { + requestPolicy: this.input.requestPolicy, + ...options, + }), + subscribe(({ data, error }) => { + this.state.data = data; + this.state.error = error; + this.state.fetching = false; + }) + ).unsubscribe; } } <${input.then}( state, - (options) => - doQuery(component, input, { requestPolicy: "network-only", ...options }), + (options) => { + component.doQuery({ requestPolicy: "network-only", ...options }); + }, )/> - - <${input.placeholder}/> - +<${input.placeholder}/> diff --git a/src/components/gql-query/impl/browser.marko b/src/components/gql-query/impl/browser.marko index ce1c894..dca1c81 100644 --- a/src/components/gql-query/impl/browser.marko +++ b/src/components/gql-query/impl/browser.marko @@ -1,6 +1,4 @@ import readyLookup from "../ready-lookup"; -import { createClient } from "../../../client"; -$ window.$$GQL || (window.$$GQL = createClient({}));
$ component.ready = true; diff --git a/src/components/gql-query/impl/server.marko b/src/components/gql-query/impl/server.marko index b76058b..1adb972 100644 --- a/src/components/gql-query/impl/server.marko +++ b/src/components/gql-query/impl/server.marko @@ -1,38 +1,7 @@ -import fetchImpl from "make-fetch-happen"; -import { createClient } from "../../../client"; -static const strictSSL = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0"; +import { getClient } from "../../../client"; -$ { - const store = - out.global.$$GQL || - (out.global.$$GQL = createClient({ - isServer: true, - fetch(url, options) { - const incomingMessage = - (out.stream && (out.stream.req || out.stream.request)) || - out.global.req || - out.global.request; - if (!incomingMessage) return fetchImpl(url, options); - - return fetchImpl( - new URL( - url, - `${incomingMessage.protocol}://${incomingMessage.headers.host}` - ).toString(), - { - ...options, - strictSSL, - headers: { - ...incomingMessage.headers, - ...options.headers, - }, - } - ); - }, - })); -}
-