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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ASSUMED_SIZE,
AUTHENTICATED,
BOOLEAN_SCALAR,
CACHE_TAG,
CHANNEL,
CHANNELS,
COMPOSE_DIRECTIVE,
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
FIELD_DEFINITION_UPPER,
FIELDS,
FOR,
FORMAT,
FROM,
IMPORT,
INACCESSIBLE,
Expand Down Expand Up @@ -92,6 +94,7 @@ import {
} from '../utils/string-constants';
import {
AUTHENTICATED_DEFINITION,
CACHE_TAG_DEFINITION,
COMPOSE_DIRECTIVE_DEFINITION,
CONFIGURE_CHILD_DESCRIPTIONS_DEFINITION,
CONFIGURE_DESCRIPTION_DEFINITION,
Expand Down Expand Up @@ -1045,3 +1048,22 @@ export const CACHE_POPULATE_DEFINITION_DATA = newDirectiveDefinitionData({
node: OPENFED_CACHE_POPULATE_DEFINITION,
optionalArgumentNames: new Set<ArgumentName>([MAX_AGE]),
});

export const CACHE_TAG_DEFINITION_DATA = newDirectiveDefinitionData({
argumentDataByName: new Map<ArgumentName, DirectiveArgumentData>([
[
FORMAT,
newDirectiveArgumentData({
directive: `@${CACHE_TAG}`,
name: FORMAT,
namedTypeKind: Kind.SCALAR_TYPE_DEFINITION,
typeNode: REQUIRED_STRING_TYPE_NODE,
}),
],
]),
isRepeatable: true,
locations: new Set<DirectiveLocation>([FIELD_DEFINITION_UPPER]),
name: CACHE_TAG,
node: CACHE_TAG_DEFINITION,
requiredArgumentNames: new Set<ArgumentName>([FORMAT]),
});
43 changes: 43 additions & 0 deletions composition/src/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type IncompatibleParentTypeMergeErrorParams,
type IncompatibleTypeWithProvidesErrorMessageParams,
type InvalidArgumentValueErrorParams,
type InvalidCacheTagArgumentTypeErrorParams,
type InvalidCustomDirectiveErrorParams,
type InvalidDirectiveLocationErrorParams,
type InvalidEntityReturnTypeErrorParams,
Expand Down Expand Up @@ -2165,3 +2166,45 @@ export function intersectingExcludeAndIncludeContractTagsError(tagNames: Array<s
`".`,
);
}

export function emptyCacheTagFormatErrorMessage(): string {
return `The argument "format" must be provided a non-empty string.`;
}

export function invalidCacheTagPlaceholderErrorMessage(placeholder: string): string {
return (
`The "format" argument defines invalid placeholder "{${placeholder}}":` +
` placeholders must be of the form "{$args.<argumentName>}", where a field of an Input Object argument` +
` is referenced by a period-delimited path, e.g. "{$args.filter.category}".`
);
}

export function invalidCacheTagBraceErrorMessage(format: string): string {
return (
`The "format" argument defines a curly brace outside of a placeholder;` +
` a curly brace is valid only as the delimiter of a placeholder, e.g. "{$args.id}".` +
` Received "${format}".`
);
}

export function invalidQueryRootFieldErrorMessage(): string {
return `The directive is valid only upon a Query root field.`;
}

export function unsupportedFieldCacheTagNamespaceErrorMessage(namespace: string): string {
return `The "format" argument defines placeholder namespace "$${namespace}", but only "$args" is supported.`;
}

export function undefinedCacheTagArgumentErrorMessage(reference: string): string {
return `The "format" argument references "$args.${reference}", which is not an argument of the field.`;
}

export function invalidCacheTagArgumentTypeErrorMessage({
reference,
typeString,
}: InvalidCacheTagArgumentTypeErrorParams): string {
return (
`The "format" argument references "$args.${reference}", which is of type "${typeString}".` +
` A referenced argument must be a single leaf value, i.e. a nullable or non-nullable scalar or Enum.`
);
}
6 changes: 5 additions & 1 deletion composition/src/errors/types/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
type SubgraphName,
type TypeName,
} from '../../types/types';
import { directlyProvidedInterfaceFieldError } from '../errors';

export type IncompatibleMergedTypesErrorParams = {
actualType: string;
Expand Down Expand Up @@ -109,3 +108,8 @@ export type InvalidEntityReturnTypeErrorParams = {
fieldCoords: string;
returnTypeName: string;
};

export type InvalidCacheTagArgumentTypeErrorParams = {
reference: string;
typeString: string;
};
8 changes: 8 additions & 0 deletions composition/src/router-configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,19 @@ export type CachePopulateConfiguration = {
operationType: OperationTypeNode;
};

export type CacheTagConfiguration = {
fieldName: FieldName;
format: string;
typeName: TypeName;
};

export type EntityCachingConfiguration = {
// Attached to the Mutation/Subscription type's ConfigurationData from @openfed__cacheInvalidate.
cacheInvalidateConfigurations: Array<CacheInvalidateConfiguration>;
// Attached to the Mutation/Subscription type's ConfigurationData from @openfed__cachePopulate.
cachePopulateConfigurations: Array<CachePopulateConfiguration>;
// Attached to the Query root type's ConfigurationData from @cacheTag.
cacheTagConfigurations: Array<CacheTagConfiguration>;
// Attached to an entity type's ConfigurationData (e.g. "Product") from @openfed__entityCache.
entityCacheConfigurations: Array<EntityCacheConfiguration>;
};
Expand Down
1 change: 1 addition & 0 deletions composition/src/router-configuration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function getOrInitializeEntityCaching(configurationData: ConfigurationDat
configurationData.entityCaching = {
cacheInvalidateConfigurations: [],
cachePopulateConfigurations: [],
cacheTagConfigurations: [],
entityCacheConfigurations: [],
};
}
Expand Down
3 changes: 3 additions & 0 deletions composition/src/utils/string-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ export const AS = 'as';
export const ASSUMED_SIZE = 'assumedSize';
export const AND_UPPER = 'AND';
export const ANY_SCALAR = '_Any';
export const ARGS = 'args';
export const ARGUMENT = 'argument';
export const AUTHENTICATED = 'authenticated';
export const ARGUMENT_DEFINITION_UPPER = 'ARGUMENT_DEFINITION';
export const BOOLEAN = 'boolean';
export const BOOLEAN_SCALAR = 'Boolean';
export const OPENFED_CACHE_INVALIDATE = 'openfed__cacheInvalidate';
export const OPENFED_CACHE_POPULATE = 'openfed__cachePopulate';
export const CACHE_TAG = 'cacheTag';
export const CHANNEL = 'channel';
export const CHANNELS = 'channels';
export const COMPOSE_DIRECTIVE = 'composeDirective';
Expand Down Expand Up @@ -62,6 +64,7 @@ export const FIELD_DEFINITION_UPPER = 'FIELD_DEFINITION';
export const FIRST_ORDINAL = '1st';
export const FLOAT_SCALAR = 'Float';
export const FOR = 'for';
export const FORMAT = 'format';
export const FRAGMENT_DEFINITION_UPPER = 'FRAGMENT_DEFINITION';
export const FRAGMENT_SPREAD_UPPER = 'FRAGMENT_SPREAD';
export const FROM = 'from';
Expand Down
8 changes: 8 additions & 0 deletions composition/src/v1/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CONNECT_FIELD_RESOLVER,
OPENFED_CACHE_INVALIDATE,
OPENFED_CACHE_POPULATE,
CACHE_TAG,
COST,
OPENFED_ENTITY_CACHE,
DEPRECATED,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
import { type DirectiveName } from '../../types/types';
import {
AUTHENTICATED_DEFINITION,
CACHE_TAG_DEFINITION,
COMPOSE_DIRECTIVE_DEFINITION,
CONFIGURE_CHILD_DESCRIPTIONS_DEFINITION,
CONFIGURE_DESCRIPTION_DEFINITION,
Expand Down Expand Up @@ -87,6 +89,7 @@ export const DIRECTIVE_DEFINITION_BY_NAME: ReadonlyMap<DirectiveName, DirectiveD
DirectiveDefinitionNode
>([
[AUTHENTICATED, AUTHENTICATED_DEFINITION],
[CACHE_TAG, CACHE_TAG_DEFINITION],
[COMPOSE_DIRECTIVE, COMPOSE_DIRECTIVE_DEFINITION],
[CONFIGURE_DESCRIPTION, CONFIGURE_DESCRIPTION_DEFINITION],
[CONFIGURE_CHILD_DESCRIPTIONS, CONFIGURE_CHILD_DESCRIPTIONS_DEFINITION],
Expand Down Expand Up @@ -148,4 +151,9 @@ export const V2_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME = new Map<DirectiveName,

export const EDFS_ARGS_REGEXP = /{{\s*args\.([a-zA-Z0-9_]+)\s*}}/g;

export const CACHE_TAG_SEGMENT_REGEXP = /{([^{}]*)}/g;

export const CACHE_TAG_PLACEHOLDER_REGEXP =
/^\s*\$([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*$/;

export const MAX_OR_SCOPES = 16;
16 changes: 16 additions & 0 deletions composition/src/v1/constants/directive-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ASSUMED_SIZE,
AUTHENTICATED,
BOOLEAN_SCALAR,
CACHE_TAG,
CHANNEL,
CHANNELS,
COMPOSE_DIRECTIVE,
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
FIELD_DEFINITION_UPPER,
FIELDS,
FOR,
FORMAT,
FROM,
IMPORT,
INACCESSIBLE,
Expand Down Expand Up @@ -871,6 +873,20 @@ export const OPENFED_ENTITY_CACHE_DEFINITION: DirectiveDefinitionNode = {
repeatable: false,
};

export const CACHE_TAG_DEFINITION: DirectiveDefinitionNode = {
arguments: [
{
kind: Kind.INPUT_VALUE_DEFINITION,
name: stringToNameNode(FORMAT),
type: REQUIRED_STRING_TYPE_NODE,
},
],
kind: Kind.DIRECTIVE_DEFINITION,
locations: stringArrayToNameNodeArray([FIELD_DEFINITION_UPPER]),
name: stringToNameNode(CACHE_TAG),
repeatable: true,
};

// @openfed__cacheInvalidate on FIELD_DEFINITION
export const OPENFED_CACHE_INVALIDATE_DEFINITION: DirectiveDefinitionNode = {
kind: Kind.DIRECTIVE_DEFINITION,
Expand Down
Loading
Loading