| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- import { Inject, OnApplicationBootstrap } from '@nestjs/common';
- import { ModuleRef } from '@nestjs/core';
- import { EventBus, Injector, PluginCommonModule, VendurePlugin } from '@vendure/core';
- import { buffer, debounceTime } from 'rxjs/operators';
- import { shopApiExtensions } from './api/api-extensions';
- import { SearchResponseFieldResolver } from './api/search-response.resolver';
- import { STELLATE_PLUGIN_OPTIONS } from './constants';
- import { StellateService } from './service/stellate.service';
- import { StellatePluginOptions } from './types';
- const StellateOptionsProvider = {
- provide: STELLATE_PLUGIN_OPTIONS,
- useFactory: () => StellatePlugin.options,
- };
- /**
- * @description
- * A plugin to integrate the [Stellate](https://stellate.co/) GraphQL caching service with your Vendure server.
- * The main purpose of this plugin is to ensure that cached data gets correctly purged in
- * response to events inside Vendure. For example, changes to a Product's description should
- * purge any associated record for that Product in Stellate's cache.
- *
- * ## Pre-requisites
- *
- * You will first need to [set up a free Stellate account](https://stellate.co/signup).
- *
- * You will also need to generate an **API token** for the Stellate Purging API. For instructions on how to generate the token,
- * see the [Stellate Purging API docs](https://docs.stellate.co/docs/purging-api#authentication).
- *
- * ## Installation
- *
- * ```
- * npm install \@vendure/stellate-plugin
- * ```
- *
- * ## Configuration
- *
- * The plugin is configured via the `StellatePlugin.init()` method. This method accepts an options object
- * which defines the Stellate service name and API token, as well as an array of {@link PurgeRule}s which
- * define how the plugin will respond to Vendure events in order to trigger calls to the
- * Stellate [Purging API](https://stellate.co/docs/graphql-edge-cache/purging-api).
- *
- * @example
- * ```ts
- * import { StellatePlugin, defaultPurgeRules } from '\@vendure/stellate-plugin';
- * import { VendureConfig } from '\@vendure/core';
- *
- * export const config: VendureConfig = {
- * // ...
- * plugins: [
- * StellatePlugin.init({
- * // The Stellate service name, i.e. `<serviceName>.stellate.sh`
- * serviceName: 'my-service',
- * // The API token for the Stellate Purging API. See the "pre-requisites" section above.
- * apiToken: process.env.STELLATE_PURGE_API_TOKEN,
- * devMode: !isProd || process.env.STELLATE_DEBUG_MODE ? true : false,
- * debugLogging: process.env.STELLATE_DEBUG_MODE ? true : false,
- * purgeRules: [
- * ...defaultPurgeRules,
- * // custom purge rules can be added here
- * ],
- * }),
- * ],
- * };
- * ```
- *
- * In your Stellate dashboard, you can use the following configuration example as a sensible default for a
- * Vendure application:
- *
- * @example
- * ```ts
- * import { Config } from "stellate";
- *
- * const config: Config = {
- * config: {
- * name: "my-vendure-server",
- * originUrl: "https://my-vendure-server.com/shop-api",
- * ignoreOriginCacheControl: true,
- * passThroughOnly: false,
- * scopes: {
- * SESSION_BOUND: "header:authorization|cookie:session",
- * },
- * headers: {
- * "access-control-expose-headers": "vendure-auth-token",
- * },
- * rootTypeNames: {
- * query: "Query",
- * mutation: "Mutation",
- * },
- * keyFields: {
- * types: {
- * SearchResult: ["productId"],
- * SearchResponseCacheIdentifier: ["collectionSlug"],
- * },
- * },
- * rules: [
- * {
- * types: [
- * "Product",
- * "Collection",
- * "ProductVariant",
- * "SearchResponse",
- * ],
- * maxAge: 900,
- * swr: 900,
- * description: "Cache Products & Collections",
- * },
- * {
- * types: ["Channel"],
- * maxAge: 9000,
- * swr: 9000,
- * description: "Cache active channel",
- * },
- * {
- * types: ["Order", "Customer", "User"],
- * maxAge: 0,
- * swr: 0,
- * description: "Do not cache user data",
- * },
- * ],
- * },
- * };
- * export default config;
- * ```
- *
- * ## Storefront setup
- *
- * In your storefront, you should point your GraphQL client to the Stellate GraphQL API endpoint, which is
- * `https://<service-name>.stellate.sh`.
- *
- * Wherever you are using the `search` query (typically in product listing & search pages), you should also add the
- * `cacheIdentifier` field to the query. This will ensure that the Stellate cache is correctly purged when
- * a Product or Collection is updated.
- *
- * @example
- * ```ts
- * import { graphql } from '../generated/gql';
- *
- * export const searchProductsDocument = graphql(`
- * query SearchProducts($input: SearchInput!) {
- * search(input: $input) {
- * // highlight-start
- * cacheIdentifier {
- * collectionSlug
- * }
- * // highlight-end
- * items {
- * # ...
- * }
- * }
- * }
- * }`);
- * ```
- *
- * ## Custom PurgeRules
- *
- * The configuration above only accounts for caching of some of the built-in Vendure entity types. If you have
- * custom entity types, you may well want to add them to the Stellate cache. In this case, you'll also need a way to
- * purge those entities from the cache when they are updated. This is where the {@link PurgeRule} comes in.
- *
- * Let's imagine that you have built a simple CMS plugin for Vendure which exposes an `Article` entity in your Shop API, and
- * you have added this to your Stellate configuration:
- *
- * @example
- * ```ts
- * import { Config } from "stellate";
- *
- * const config: Config = {
- * config: {
- * // ...
- * rules: [
- * // ...
- * {
- * types: ["Article"],
- * maxAge: 900,
- * swr: 900,
- * description: "Cache Articles",
- * },
- * ],
- * },
- * // ...
- * };
- * export default config;
- * ```
- *
- * You can then add a custom {@link PurgeRule} to the StellatePlugin configuration:
- *
- * @example
- * ```ts
- * import { StellatePlugin, defaultPurgeRules } from "\@vendure/stellate-plugin";
- * import { VendureConfig } from "\@vendure/core";
- * import { ArticleEvent } from "./plugins/cms/events/article-event";
- *
- * export const config: VendureConfig = {
- * // ...
- * plugins: [
- * StellatePlugin.init({
- * // ...
- * purgeRules: [
- * ...defaultPurgeRules,
- * new PurgeRule({
- * eventType: ArticleEvent,
- * handler: async ({ events, stellateService }) => {
- * const articleIds = events.map((e) => e.article.id);
- * stellateService.purge("Article", articleIds);
- * },
- * }),
- * ],
- * }),
- * ],
- * };
- * ```
- *
- * ## DevMode & Debug Logging
- *
- * In development, you can set `devMode: true`, which will prevent any calls being made to the Stellate Purging API.
- *
- * If you want to log the calls that _would_ be made to the Stellate Purge API when in devMode, you can set `debugLogging: true`.
- * Note that debugLogging generates a lot of debug-level logging, so it is recommended to only enable this when needed.
- *
- * @example
- * ```ts
- * import { StellatePlugin, defaultPurgeRules } from '\@vendure/stellate-plugin';
- * import { VendureConfig } from '\@vendure/core';
- *
- * export const config: VendureConfig = {
- * // ...
- * plugins: [
- * StellatePlugin.init({
- * // ...
- * devMode: !process.env.PRODUCTION,
- * debugLogging: process.env.STELLATE_DEBUG_MODE ? true : false,
- * purgeRules: [
- * ...defaultPurgeRules,
- * ],
- * }),
- * ],
- * };
- * ```
- *
- *
- * @since 2.1.5
- * @docsCategory core plugins/StellatePlugin
- */
- @VendurePlugin({
- imports: [PluginCommonModule],
- providers: [StellateOptionsProvider, StellateService],
- shopApiExtensions: {
- schema: shopApiExtensions,
- resolvers: [SearchResponseFieldResolver],
- },
- compatibility: '^3.0.0',
- })
- export class StellatePlugin implements OnApplicationBootstrap {
- static options: StellatePluginOptions;
- static init(options: StellatePluginOptions) {
- this.options = options;
- return this;
- }
- constructor(
- @Inject(STELLATE_PLUGIN_OPTIONS) private options: StellatePluginOptions,
- private eventBus: EventBus,
- private stellateService: StellateService,
- private moduleRef: ModuleRef,
- ) {}
- onApplicationBootstrap() {
- const injector = new Injector(this.moduleRef);
- for (const purgeRule of this.options.purgeRules ?? []) {
- const source$ = this.eventBus.ofType(purgeRule.eventType);
- source$
- .pipe(
- buffer(
- source$.pipe(
- debounceTime(purgeRule.bufferTimeMs ?? this.options.defaultBufferTimeMs ?? 2000),
- ),
- ),
- )
- .subscribe(events =>
- purgeRule.handle({ events, injector, stellateService: this.stellateService }),
- );
- }
- }
- }
|