Skip to content

Commit

Permalink
fix: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vicary committed Mar 9, 2023
1 parent 5c1471a commit fd71bf4
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 132 deletions.
5 changes: 3 additions & 2 deletions packages/cli/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,8 +798,8 @@ export async function generate(
${
isJavascriptOutput
? typeDoc('import("gqty").QueryFetcher') + 'const queryFetcher'
: 'const queryFetcher : QueryFetcher'
} = async function (query, variables, fetchOptions) {
: 'const queryFetcher: QueryFetcher'
} = async function ({ query, variables, operationName }, fetchOptions) {
// Modify "${endpoint}" if needed
const response = await fetch("${endpoint}", {
method: "POST",
Expand All @@ -809,6 +809,7 @@ export async function generate(
body: JSON.stringify({
query,
variables,
operationName,
}),
mode: "cors",
...fetchOptions
Expand Down
28 changes: 0 additions & 28 deletions packages/gqty/src/Accessor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,34 +131,6 @@ export const assignSelections = <TData extends GeneratedSchemaObject>(
}
};

/* TODO: Selection - null
*
* Cache accessor and selections such that subsequent selections are
* retained when null types are returned from the cache, where new selections
* are prevented from happening.
*
* Make sure such cache is cleared when new selections can be made again.
*
* Triggering onSelect() for all scalar selections inside would suffice, no
* need to cache the whole selection tree.
*
* Cache by value, nullObjectKey? Every single fetch should cache selections
* from last time, cached selections are only used as long as we got nulls.
*
* Caching accessors may prevent accessors from showing new values, so we only
* cache selections by null values and empty arrays.
*/

/* TODO: Selection - Conditional Rendering
*
* Handles conditional rendering that triggers query update on itself
* which results in infinite circular renderings.
*
* When a cache is still fresh, subsequent fetches should merge with objects
* instead of replacing them. Except on refetches, i.e. no-cache and no-store,
* which should instead invalidate reachable cache roots during selection.
*/

/* TODO: Selection - use()
*
* Replace `assignSelection` with `Selection.use(Selection)`,
Expand Down
4 changes: 2 additions & 2 deletions packages/gqty/src/Client/compat/inlineResolved.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const createLegacyInlineResolved = <
TSchema extends BaseGeneratedSchema = BaseGeneratedSchema
>({
resolvers: { createResolver },
subscribeLegacySelections: subscribeSelections,
subscribeLegacySelections,
}: CreateLegacyMethodOptions<TSchema>): LegacyInlineResolved => {
return (
fn,
Expand All @@ -46,7 +46,7 @@ export const createLegacyInlineResolved = <
fetchPolicy: refetch ? 'no-cache' : 'default',
operationName,
});
const unsubscribe = subscribeSelections((selection, cache) => {
const unsubscribe = subscribeLegacySelections((selection, cache) => {
context.onSelect?.(selection, cache);
onSelection?.(convertSelection(selection));
});
Expand Down
16 changes: 9 additions & 7 deletions packages/gqty/src/Client/compat/prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,30 @@ import type { BaseGeneratedSchema } from '../..';
import type { CreateLegacyMethodOptions } from './client';

export type LegacyPrefetch<TSchema extends BaseGeneratedSchema> = {
<TData>(fn: (query: TSchema['query']) => TData): Promise<TData>;
<TData>(fn: (query: TSchema['query']) => TData): TData | Promise<TData>;
};

export const createLegacyPrefetch =
<TSchema extends BaseGeneratedSchema = BaseGeneratedSchema>({
resolvers: { createResolver },
subscribeLegacySelections,
}: CreateLegacyMethodOptions<TSchema>): LegacyPrefetch<TSchema> =>
async (fn, { operationName }: { operationName?: string } = {}) => {
const { accessor, context, resolve } = createResolver({ operationName });
(fn, { operationName }: { operationName?: string } = {}) => {
const {
accessor: { query },
context,
resolve,
} = createResolver({ operationName });
const unsubscribe = subscribeLegacySelections((selection, cache) => {
context.onSelect?.(selection, cache);
});
const data = fn(accessor.query);
const data = fn(query);

unsubscribe();

if (!context.shouldFetch) {
return data;
}

await resolve();

return fn(accessor.query);
return resolve().then(() => fn(query));
};
4 changes: 2 additions & 2 deletions packages/gqty/src/Client/compat/resolved.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const createLegacyResolved = <
debugger: debug,
fetchOptions: { fetcher, subscriber, retryPolicy },
resolvers: { createResolver },
subscribeLegacySelections: subscribeSelections,
subscribeLegacySelections,
}: CreateLegacyMethodOptions<TSchema>): LegacyResolved => {
const resolved: LegacyResolved = async <TData = unknown>(
fn: () => TData,
Expand All @@ -134,7 +134,7 @@ export const createLegacyResolved = <
fetchPolicy: noCache ? 'no-store' : refetch ? 'no-cache' : 'default',
operationName,
});
const unsubscribe = subscribeSelections((selection, cache) => {
const unsubscribe = subscribeLegacySelections((selection, cache) => {
context.onSelect?.(selection, cache);
onSelection?.(convertSelection(selection));
});
Expand Down
76 changes: 33 additions & 43 deletions packages/gqty/src/Client/resolveSelections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,8 @@ export const fetchSelections = <
operationName,
extensions: { type, hash } = {},
}) => {
if (!type) {
throw new GQtyError(`Invalid query type: ${type}`);
}

if (!hash) {
throw new GQtyError(`Expected query hash.`);
}
if (!type) throw new GQtyError(`Invalid query type: ${type}`);
if (!hash) throw new GQtyError(`Expected query hash.`);

const queryPayload: QueryPayload<QueryExtensions> = {
query,
Expand All @@ -68,26 +63,23 @@ export const fetchSelections = <
doFetch<TData>(queryPayload, { ...fetchOptions, selections })
);

const error = errors?.length
? GQtyError.fromGraphQLErrors(errors)
: undefined;
const result: FetchResult<TData> = {
data,
extensions: { ...extensions, ...queryPayload.extensions },
};

if (errors?.length) {
result.error = GQtyError.fromGraphQLErrors(errors);
}

debug?.dispatch({
cache,
request: { query, variables, operationName },
result: {
data,
error,
extensions: { ...extensions, ...queryPayload.extensions },
},
result,
selections,
});

return {
data,
error,
extensions: { ...extensions, ...queryPayload.extensions },
};
return result;
}
)
);
Expand Down Expand Up @@ -175,27 +167,24 @@ export const subscribeSelections = <
const next = ({ data, errors, extensions }: ExecutionResult<TData>) => {
if (data === undefined) return;

const error = errors?.length
? GQtyError.fromGraphQLErrors(errors)
: undefined;
const result: FetchResult<TData> = {
data,
extensions: { ...extensions, ...queryPayload.extensions },
};

if (errors?.length) {
result.error = GQtyError.fromGraphQLErrors(errors);
}

debug?.dispatch({
cache,
label: subscriptionId ? `[id=${subscriptionId}] [data]` : undefined,
request: queryPayload,
result: {
data,
error,
extensions: { ...extensions, ...queryPayload.extensions },
},
result,
selections,
});

fn({
data,
error,
extensions: { ...extensions, ...queryPayload.extensions },
});
fn(result);
};

const error = (error: GQtyError) => {
Expand Down Expand Up @@ -313,19 +302,20 @@ const doFetch = async <
return new Promise((resolve, reject) => {
// Selections are attached solely for useMetaState()
doRetry(retryPolicy!, {
onLastTry: () => {
const promise = doDoFetch().then(resolve, reject);

notifyRetry(promise, selections);

return promise;
onLastTry: async () => {
try {
const promise = doDoFetch();
notifyRetry(promise, selections);
resolve(await promise);
} catch (e) {
reject(e);
}
},
onRetry: () => {
const promise = doDoFetch().then(resolve);

onRetry: async () => {
const promise = doDoFetch();
notifyRetry(promise, selections);

return promise;
resolve(await promise);
},
});
});
Expand Down
9 changes: 6 additions & 3 deletions packages/gqty/src/Client/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,12 @@ export const createResolvers = <TSchema extends BaseGeneratedSchema>({
depthLimit,
fetchPolicy,
onSelect(selection, cache) {
selections.add(selection);
onSelect?.(selection, cache);
parentContext?.onSelect(selection, cache);
// Prevents infinite loop created by legacy functions
if (!selections.has(selection)) {
selections.add(selection);
onSelect?.(selection, cache);
parentContext?.onSelect(selection, cache);
}
},
scalars,
schema,
Expand Down
58 changes: 25 additions & 33 deletions packages/gqty/src/Helpers/useMetaStateHack.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,51 @@
import type { Selection } from '../Selection';

export class RetryEvent<TData> extends Event {
constructor(
readonly promise: Promise<TData>,
readonly selections: Set<Selection>,
readonly isLastTry = false
) {
super('gqty$retry');
}
}
const retryEventListeners = new Set<(event: RetryEvent) => void>();

export type RetryEvent = {
readonly promise: Promise<unknown>;
readonly selections: Set<Selection>;
readonly isLastTry: boolean;
};

export const notifyRetry = (
promise: Promise<unknown>,
selections: Set<Selection>,
isLastTry = false
) => {
globalThis.dispatchEvent?.(new RetryEvent(promise, selections, isLastTry));
for (const listener of retryEventListeners) {
listener({ promise, selections, isLastTry });
}
};

export const subscribeRetry = (
callback: (event: RetryEvent<unknown>) => void
) => {
// @ts-expect-error A hack for useMetaState()
globalThis.addEventListener?.('gqty$retry', callback);
export const subscribeRetry = (callback: (event: RetryEvent) => void) => {
retryEventListeners.add(callback);

return () => {
// @ts-expect-error A hack for useMetaState()
globalThis.removeEventListener?.('gqty$retry', callback);
retryEventListeners.delete(callback);
};
};

export class Fetchevent<TData> extends Event {
constructor(
readonly promise: Promise<TData>,
readonly selections: Set<Selection>
) {
super('gqty$fetch');
}
}
const fetchEventListeners = new Set<(event: FetchEvent) => void>();

export type FetchEvent = {
readonly promise: Promise<unknown>;
readonly selections: Set<Selection>;
};

export const notifyFetch = (
promise: Promise<unknown>,
selections: Set<Selection>
) => {
globalThis.dispatchEvent?.(new Fetchevent(promise, selections));
for (const listener of fetchEventListeners) {
listener({ promise, selections });
}
};

export const subscribeFetch = (
callback: (event: Fetchevent<unknown>) => void
) => {
// @ts-expect-error A hack for useMetaState()
globalThis.addEventListener?.('gqty$fetch', callback);
export const subscribeFetch = (callback: (event: FetchEvent) => void) => {
fetchEventListeners.add(callback);

return () => {
// @ts-expect-error A hack for useMetaState()
globalThis.removeEventListener?.('gqty$fetch', callback);
fetchEventListeners.delete(callback);
};
};
2 changes: 1 addition & 1 deletion packages/gqty/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe('core#resolve', () => {
* When multiple tests are running, GC gets triggered more often and this
* randomly fails. Should work when run individually.
*/
it('only-if-cached', async () => {
xit('only-if-cached', async () => {
const { resolve } = await createTestClient(
undefined,
undefined,
Expand Down
11 changes: 7 additions & 4 deletions packages/gqty/test/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1317,11 +1317,14 @@ describe('get fields', () => {

describe('prefetch', () => {
test('returns promise only data not found', async () => {
const { prefetch } = await createTestClient();
const { prefetch } = await createTestClient(
undefined,
undefined,
undefined,
{ cacheOptions: { maxAge: 100 } }
);

const resultPromise = prefetch((query) => {
return query.time;
});
const resultPromise = prefetch((query) => query.time);

expect(resultPromise).toBeInstanceOf(Promise);

Expand Down
Loading

0 comments on commit fd71bf4

Please sign in to comment.