google / schema-dts

JSON-LD TypeScript types for Schema.org vocabulary
Apache License 2.0
883 stars 33 forks source link

Add support for custom attributes #114

Open sebastienlabine opened 4 years ago

sebastienlabine commented 4 years ago

Problem Sometimes, we need to input a custom attribute in the Schema. For instance the sitelinks-searchbox structured data requires a query-input attribute that is not available in the schema.org representation. However, I can't add it manually, because the compiler will throw:

Object literal may only specify known properties, and '"query-input"' does not exist in type 'SearchActionLeaf'

potentialAction: {
    "@id":  `${process.env.NEXT_PUBLIC_WEBSITE_URL}/explore`,
    "@type": "SearchAction",
     target: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/explore/results?q={search_term_string}`,
    "query-input": "required name=search_term_string"
}

Solution Add an attribute that let's you input a custom key value pair.

declare type ThingBase = Partial<IdReference> & {
    /** An additional type for the item, typically used for adding more specific types from external vocabularies in microdata syntax. This is a relationship between something and a class that the thing is in. In RDFa syntax, it is better to use the native RDFa syntax - the 'typeof' attribute - for multiple types. Schema.org tools may have only weaker understanding of extra types, in particular those defined externally. */
    "additionalType"?: SchemaValue<URL>;
    /** An alias for the item. */
    "alternateName"?: SchemaValue<Text>;
    /** A description of the item. */
    "description"?: SchemaValue<Text>;
    /** A sub property of description. A short description of the item used to disambiguate from other, similar items. Information from other properties (in particular, name) may be necessary for the description to be useful for disambiguation. */
    "disambiguatingDescription"?: SchemaValue<Text>;
    /** The identifier property represents any kind of identifier for any kind of {@link http://schema.org/Thing Thing}, such as ISBNs, GTIN codes, UUIDs etc. Schema.org provides dedicated properties for representing many of these, either as textual strings or as URL (URI) links. See {@link /docs/datamodel.html#identifierBg background notes} for more details. */
    "identifier"?: SchemaValue<PropertyValue | Text | URL | IdReference>;
    /** An image of the item. This can be a {@link http://schema.org/URL URL} or a fully described {@link http://schema.org/ImageObject ImageObject}. */
    "image"?: SchemaValue<ImageObject | URL | IdReference>;
    /** Indicates a page (or other CreativeWork) for which this thing is the main entity being described. See {@link /docs/datamodel.html#mainEntityBackground background notes} for details. */
    "mainEntityOfPage"?: SchemaValue<CreativeWork | URL | IdReference>;
    /** The name of the item. */
    "name"?: SchemaValue<Text>;
    /** Indicates a potential Action, which describes an idealized action in which this thing would play an 'object' role. */
    "potentialAction"?: SchemaValue<Action | IdReference>;
    /** URL of a reference Web page that unambiguously indicates the item's identity. E.g. the URL of the item's Wikipedia page, Wikidata entry, or official website. */
    "sameAs"?: SchemaValue<URL>;
    /** A CreativeWork or Event about this Thing. */
    "subjectOf"?: SchemaValue<CreativeWork | Event | IdReference>;
    /** URL of the item. */
    "url"?: SchemaValue<URL>;
    [extra: string]: string | ThingBase<---------------------------------------
};
Eyas commented 4 years ago

FWIW one workaround is mentioned in #33

This is a tough one to figure out, because [extra: string]: string | ThingBase effectively removes a lot of type checking that we talk about, e.g. if you type nmae instead of name, etc.

The two critical user journeys we want to address are (a) the IDE / completions experience and (b) tsc typechecking for obviously wrong/accidental input. Extra props remove (b), which might be an issue.

Writing your own XyzAction type especially works neatly if you're saving it as an intermediate variable. Since TS's excess property check only applies to object literals. So if you define this as:

// Available actions:
type QueryAction = SearchAction & {
  "query-input": string;
};

export const EXPLORE_ACTION: QueryAction = {
    "@id":  `${process.env.NEXT_PUBLIC_WEBSITE_URL}/explore`,
    "@type": "SearchAction",
     target: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/explore/results?q={search_term_string}`,
    "query-input": "required name=search_term_string"
};

// Elsewhere:

/* ... */ {
  // ...
  potentialAction: EXPLORE_ACTION
};

Does that work?

sebastienlabine commented 4 years ago

That's what I was afraid of. I didn't really know how to keep both theses user journeys while including custom attributes.

The solution you provided should be good! I don't know how often we might need to create our own action but in my opinion we would benifit from having documentation on how to include custom attributes by creating custom types.

lilouartz commented 4 months ago

Can I just ignore the TypeScript error?

This is what I did for https://pillser.com

export const meta: MetaFunction = () => {
  return [
    { title: 'Pillser – Supplement Comparison Site' },
    {
      content: 'The best value for your money supplements.',
      name: 'description',
    },
    {
      href: 'https://pillser.com/',
      rel: 'canonical',
      tagName: 'link',
    },
    {
      '@context': 'https://schema.org',
      '@type': 'WebSite',
      description:
        'The best value for your money supplements. Find and compare supplements to get the most effective product for the best price.',
      name: 'Pillser – Supplement Comparison Site',
      potentialAction: {
        '@type': 'SearchAction',
        // @ts-expect-error - https://github.com/google/schema-dts/issues/114
        'query-input': 'required name=search_term_string',
        target: {
          '@type': 'EntryPoint',
          urlTemplate: 'https://pillser.com/search?q={search_term_string}',
        },
      },
      publisher: {
        '@type': 'Organization',
        logo: {
          '@type': 'ImageObject',
          url: 'https://www.pillser.com/pillser-logo.png',
        },
        name: 'Pillser',
      },
      url: 'https://www.pillser.com',
    } satisfies WithContext<WebSite>,
  ];
};

... or is this going to backfire somehow?