Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slow-buses-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gqty': minor
---

Expose aliasGenerator option and default alias generators
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ public-hoist-pattern[]=rollup*
public-hoist-pattern[]=@rollup*
prefer-workspace-packages=true
stream=true
@jsr:registry=https://npm.jsr.io
9 changes: 8 additions & 1 deletion examples/solid/src/gqty/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
*/

import { createSolidClient } from '@gqty/solid';
import { Cache, GQtyError, createClient, type QueryFetcher } from 'gqty';
import {
Cache,
GQtyError,
createClient,
createDebugAliasHasher,
type QueryFetcher,
} from 'gqty';
import {
generatedSchema,
scalarsEnumsHash,
Expand Down Expand Up @@ -62,6 +68,7 @@ const cache = new Cache(
);

export const client = createClient<GeneratedSchema>({
aliasGenerator: createDebugAliasHasher(6),
schema: generatedSchema,
scalars: scalarsEnumsHash,
cache,
Expand Down
17 changes: 15 additions & 2 deletions internal/test-utils/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from 'assert';
import {
Cache,
createClient,
GQtyError,
type BaseGeneratedSchema,
type GQtyClient,
type QueryPayload,
Expand Down Expand Up @@ -295,13 +296,25 @@ export const createInMemoryClient = async <TSchema extends BaseGeneratedSchema>(
fetcher: async ({ query, variables, operationName }) => {
await options.onFetch?.({ query, variables, operationName });

const res = await executor({
const result = await executor({
document: parse(query),
variables,
operationName,
}).then((result) => {
if (Symbol.asyncIterator in result) {
return result[Symbol.asyncIterator]()
.next()
.then((res) => res.value as ExecutionResult<any, any>);
} else {
return result;
}
});

return res as never;
if (result.errors?.length) {
throw GQtyError.fromGraphQLErrors(result.errors);
}

return result;
},
subscriber: new MockWsClient(executor),
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@
"engines": {
"pnpm": "^8.10.0"
},
"packageManager": "[email protected].6+sha256.01c01eeb990e379b31ef19c03e9d06a14afa5250b82e81303f88721c99ff2e6f"
"packageManager": "[email protected].9+sha512.499434c9d8fdd1a2794ebf4552b3b25c0a633abcee5bb15e7b5de90f32f47b513aca98cd5cfd001c31f0db454bc3804edccd578501e4ca293a6816166bbd9f81"
}
3 changes: 1 addition & 2 deletions packages/gqty/src/Accessor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export function createSchemaAccessor<TSchema extends BaseGeneratedSchema>(
return;

const selection =
selectionCache.get(key) ??
Selection.createRoot(key, { aliasLength: context.aliasLength });
selectionCache.get(key) ?? Selection.createRoot(key);

selectionCache.set(key, selection);

Expand Down
39 changes: 27 additions & 12 deletions packages/gqty/src/Accessor/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type GeneratedSchemaObject,
type Type,
} from '../Schema';
import type { Selection } from '../Selection';
import type { Selection, SelectionInput } from '../Selection';
import { isPlainObject } from '../Utils';
import type { Meta } from './meta';
import { $meta } from './meta';
Expand Down Expand Up @@ -253,15 +253,30 @@ const objectProxyHandler: ProxyHandler<GeneratedSchemaObject> = {

const { __args, __type } = targetType;
if (__args) {
return (args?: Record<string, unknown>) =>
resolve(
proxy,
meta.selection.getChild(
key,
args ? { input: { types: __args!, values: args } } : {}
),
__type
return (args?: Record<string, unknown>) => {
const keys = meta.selection.ancestry
.map((s) => s.key.toString())
.concat(key);
const alias = meta.context.aliasGenerator?.(keys, args);
const input: SelectionInput = {};

if (args) {
for (const key in args) {
input[key] = {
alias: alias?.input[key],
type: __args[key],
value: args[key],
};
}
}

const child = meta.selection.getChild(
key,
args ? { alias: alias?.field, input } : {}
);

return resolve(proxy, child, __type);
};
}

return resolve(proxy, meta.selection.getChild(key), __type);
Expand Down Expand Up @@ -468,7 +483,7 @@ const selectIdentityFields = (

// Always __typename except inside interfaces and unions
if (parent?.key !== '$on') {
accessor.__typename;
Reflect.get(accessor, '__typename');
}

const keys = getIdentityFields(meta);
Expand All @@ -479,7 +494,7 @@ const selectIdentityFields = (
// Already selected at the common root of this interface/union.
if (isUnion && parent?.parent?.children.has(key)) continue;

accessor[key];
Reflect.get(accessor, key);
}
};

Expand All @@ -506,7 +521,7 @@ const arrayProxyHandler: ProxyHandler<CacheObject[]> = {
throw new GQtyError(`Cache data must be an array.`);
}

if (key === 'length') proxy[0];
if (key === 'length') Reflect.get(proxy, 0);

const numKey = +key;
if (!isNaN(numKey) && numKey < data.length) {
Expand Down
67 changes: 67 additions & 0 deletions packages/gqty/src/Client/alias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { hash } from '../Utils/hash';

export type AliasGenerator = (
/**
* The chain of selection keys from query root, a unique alias is generated
* based on they provided keys and arguments.
*
* @example ["query", "foo", "bar"]
*/
keys: string[],

/**
* GraphQL arguments related to the current selection, a unique alias is
* generated based on they provided keys and arguments.
*/
args?: Record<string, unknown>
) => SelectionAlias;

export type SelectionAlias = {
/**
* Alias of the current selection field.
*/
field: string;

/**
* Variable name to aliases mapping.
*/
input: Record<string, string | undefined>;
};

export const createAliasHasher =
(maxLength = Infinity): AliasGenerator =>
(keys, args) => {
const field = hash({ key: keys.at(-1), ...args }).slice(0, maxLength);
const input: Record<string, string> = {};

if (args) {
for (const key in args) {
input[key] = hash(`${field}_${key}`).slice(0, maxLength);
}
}

return {
field,
input,
};
};

export const createDebugAliasHasher =
(maxLength = Infinity): AliasGenerator =>
(keys, args) => {
const field = keys
.concat(hash({ key: keys.at(-1), args }).slice(0, maxLength))
.join('_');
const input: Record<string, string> = {};

if (args) {
for (const key in args) {
input[key] = keys.concat(key, field).join('_');
}
}

return {
field,
input,
};
};
27 changes: 19 additions & 8 deletions packages/gqty/src/Client/compat/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ export class LegacySelection {
const isInterfaceUnionSelection = key === '$on';

this.cachePath = isInterfaceUnionSelection
? prevSelection?.cachePath ?? []
? (prevSelection?.cachePath ?? [])
: prevSelection
? [...prevSelection.cachePath, pathKey]
: [pathKey];
? [...prevSelection.cachePath, pathKey]
: [pathKey];

this.pathString = isInterfaceUnionSelection
? prevSelection?.pathString ?? ''
? (prevSelection?.pathString ?? '')
: `${prevSelection?.pathString.concat('.') ?? ''}${pathKey}`;

const prevSelectionsList = prevSelection?.selectionsList ?? [];
Expand Down Expand Up @@ -129,21 +129,32 @@ export const convertSelection = (
selectionId = 0,
operationName?: string
): LegacySelection => {
const args: Record<string, unknown> = {};
const argTypes: Record<string, string> = {};

if (selection.input) {
for (const key in selection.input) {
const { type, value } = selection.input[key];
args[key] = value;
argTypes[key] = type;
}
}

return new LegacySelection({
id: ++selectionId,
key: selection.key,
// translate the whole selection chain upwards
prevSelection: selection.parent
? convertSelection(selection.parent, selectionId, operationName)
: undefined,
args: selection.input?.values,
argTypes: selection.input?.types,
args,
argTypes,
type:
selection.root.key === 'query'
? LegacySelectionType.Query
: selection.root.key === 'mutation'
? LegacySelectionType.Mutation
: LegacySelectionType.Subscription,
? LegacySelectionType.Mutation
: LegacySelectionType.Subscription,
operationName,
alias: selection.alias,
unions: selection.isUnion ? [selection.key.toString()] : undefined,
Expand Down
9 changes: 5 additions & 4 deletions packages/gqty/src/Client/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Disposable } from '../Disposable';
import type { Resetable } from '../Resetable';
import type { ScalarsEnumsHash, Schema } from '../Schema';
import type { Selectable } from '../Selectable';
import type { AliasGenerator } from './alias';

export type SchemaContext<
T extends Record<string, unknown> = Record<string, unknown>,
Expand All @@ -11,7 +12,7 @@ export type SchemaContext<
Resetable &
Selectable & {
cache: Cache;
readonly aliasLength?: number;
readonly aliasGenerator?: AliasGenerator;
readonly cacheOptions?: CacheGetOptions;
readonly depthLimit: number;
readonly scalars: ScalarsEnumsHash;
Expand All @@ -26,7 +27,7 @@ export type SchemaContext<
};

export type CreateContextOptions = {
aliasLength?: number;
aliasGenerator?: AliasGenerator;
cache: Cache;
depthLimit: number;
cachePolicy: RequestCache;
Expand All @@ -36,7 +37,7 @@ export type CreateContextOptions = {
};

export const createContext = ({
aliasLength,
aliasGenerator,
cache,
cachePolicy,
depthLimit,
Expand All @@ -48,7 +49,7 @@ export const createContext = ({
const selectSubscriptions = new Set<Selectable['select']>();

return {
aliasLength,
aliasGenerator,
cache:
cachePolicy === 'no-cache' ||
cachePolicy === 'no-store' ||
Expand Down
18 changes: 13 additions & 5 deletions packages/gqty/src/Client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
ScalarsEnumsHash,
Schema,
} from '../Schema';
import { createAliasHasher, type AliasGenerator } from './alias';
import {
createLegacyClient,
type LegacyClient,
Expand All @@ -21,9 +22,7 @@ import { createContext } from './context';
import { createDebugger, type Debugger } from './debugger';
import { createResolvers, type Resolvers } from './resolvers';

export { $meta } from '../Accessor';
export { getFields, prepass, selectFields } from '../Helpers';
export * as useMetaStateHack from '../Helpers/useMetaStateHack';
export * from './alias';
export type {
LegacyClientOptions as LegacyFetchers,
LegacyHydrateCache,
Expand Down Expand Up @@ -107,9 +106,17 @@ export type ClientOptions = {
* when collisions occur, specify Infinity here to use the full hash.
*
* @default 6
* @deprecated Use `aliasGenerator` instead.
*/
aliasLength?: number;

/**
* Alias generator function, useful for debugging and logging.
*
* This option takes precedence over `aliasLength`.
*/
aliasGenerator?: AliasGenerator;

/**
* Milliseconds to wait before pending queries are batched up for fetching.
*/
Expand Down Expand Up @@ -164,6 +171,7 @@ export const createClient = <
_ObjectTypes extends SchemaObjects<TSchema> = never,
>({
aliasLength = 6,
aliasGenerator = createAliasHasher(aliasLength),
batchWindow,
// This default cache on a required option is for legacy clients, which does
// not provide a `cache` option.
Expand Down Expand Up @@ -206,7 +214,7 @@ export const createClient = <

// Global scope for accessing the cache via `schema` property.
const clientContext = createContext({
aliasLength,
aliasGenerator,
cache,
depthLimit: __depthLimit,
cachePolicy: fetchPolicy,
Expand All @@ -216,7 +224,7 @@ export const createClient = <
});

const resolvers = createResolvers<TSchema>({
aliasLength,
aliasGenerator,
batchWindow,
scalars,
schema,
Expand Down
Loading
Loading