├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── index.ts ├── runtime │ ├── cache-service.ts │ ├── inuxt-delivery-client-interface.ts │ ├── kenticokontent-config.js │ └── plugin.template.ts └── utilties │ └── logger.ts ├── tests └── module.test.js ├── tsconfig.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/kenticokontentnuxtmodule 5 | docker: 6 | - image: circleci/node:16.13.0 7 | steps: 8 | # Checkout repository 9 | - checkout 10 | 11 | - run: 12 | name: Check current version of node 13 | command: node -v 14 | 15 | # Restore cache 16 | - restore_cache: 17 | key: yarn-{{ checksum "yarn.lock" }} 18 | 19 | # Install dependencies 20 | - run: 21 | name: Install Dependencies 22 | command: NODE_ENV=dev yarn 23 | 24 | # Keep cache 25 | - save_cache: 26 | key: yarn-{{ checksum "yarn.lock" }} 27 | paths: 28 | - "node_modules" 29 | 30 | # build 31 | - run: yarn build 32 | 33 | # Test 34 | # - run: yarn test 35 | 36 | # Release 37 | - run: 38 | name: release 39 | command: npm run semantic-release || true 40 | secrets: [ npm_token, gh_token ] -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Common 2 | node_modules 3 | dist 4 | .nuxt 5 | coverage 6 | 7 | # Plugin 8 | lib/plugin.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs/eslint-config-typescript" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | #visual studio code 21 | .vscode 22 | 23 | #personal help file 24 | .commands 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | # visual studio 2019 67 | .vs 68 | 69 | #custom 70 | coverage 71 | dist 72 | 73 | commands.txt 74 | 75 | /dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/Domitnator/kentico-kontent-nuxt-module.svg?style=svg&circle-token=ca67cac592202e6584670a87c3ace63abe9ef36a)](https://circleci.com/gh/Domitnator/kentico-kontent-nuxt-module) 2 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 3 | [![NPM](https://nodei.co/npm/kentico-kontent-nuxt-module.png?mini=true)](https://npmjs.org/package/kentico-kontent-nuxt-module) 4 | 5 | # kentico-kontent-nuxt-module 6 | Add Kentico Kontent super power to your nuxt app :fire: 7 | 8 | ## Features 9 | 10 | The module makes it easy to do delivery client api calls via the [Kentico kontent Delivery JS SDK](https://github.com/Kentico/kontent-delivery-sdk-js/blob/master/readme.md). 11 | 12 | ## Quick start 13 | - Install via npm 14 | 15 | ``` 16 | npm i kentico-kontent-nuxt-module --save 17 | ``` 18 | 19 | - Add `kentico-kontent-nuxt-module` to `modules` section of `nuxt.config.js` 20 | 21 | ```json 22 | /* 23 | ** Nuxt.js modules 24 | */ 25 | modules: [ 26 | 'kentico-kontent-nuxt-module' 27 | ], 28 | kenticokontent: { 29 | projectId: 'xxxx-xxx-xxxx-xxxx-xxxxx', 30 | enableAdvancedLogging: false, 31 | previewApiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', 32 | globalQueryConfig: { 33 | usePreviewMode: true, // Queries the Delivery Preview API. 34 | useSecureMode: false, 35 | }, 36 | baseUrl: 'https://custom.uri/api/KenticoKontentProxy', 37 | secureApiKey: 'xxx', 38 | enableSecuredMode: true 39 | }, 40 | ``` 41 | > Note: See [the client configuration section](https://github.com/Kentico/kontent-delivery-sdk-js/blob/master/DOCS.md#client-configuration) of the Kentico Kontent Delivery SDK for all available configuration options. 42 | 43 | 44 | - $nuxtDeliveryClient is now globally available. 45 | 46 | ```javascript 47 | 48 | this.$nuxtDeliveryClient.items() 49 | .type('page') 50 | .toPromise() 51 | .then(response => console.log('DeliveryClient Response', response)); 52 | 53 | ``` 54 | ### Note: 55 | By default Nuxt can only work with promises. Therefor you always use the "toPromise" method provided by the Kentico Kontent Delivery SDK! RxJs operator's are not supported at the moment. 56 | 57 | # Typescript 58 | 59 | Since version 7 the kentico-kontent-nuxt-module has typescript support! 60 | 61 | Add the types to your "types" array in tsconfig.json after the @nuxt/types (Nuxt 2.9.0+) or @nuxt/vue-app entry 62 | 63 | ```json 64 | 65 | { 66 | "compilerOptions": { 67 | "types": [ 68 | "@nuxt/types", 69 | "kentico-kontent-nuxt-module" 70 | ] 71 | } 72 | } 73 | 74 | ``` 75 | 76 | ## Generating 77 | When using a static generated deployment you may need to use the [items-feed](https://docs.kontent.ai/reference/api-changelog#a-delivery-api-limitation) endpoint when generating your site (because the items endpoint has a rate limitation). 78 | 79 | ```javascript 80 | 81 | this.$nuxtDeliveryClient.itemsFeedAll() 82 | .toPromise() 83 | .then(response => console.log('DeliveryClient Response', response)); 84 | 85 | ``` 86 | 87 | ## Caching 88 | API calls can be "cached" (they will be stored in memory) client side via the "viaCache" method. 89 | 90 | ```javascript 91 | 92 | const query = this.$nuxtDeliveryClient.items().type('page'); 93 | const cacheSeconds = 30; 94 | this.$nuxtDeliveryClient.viaCache(query, cacheSeconds) 95 | .then(response => console.log('DeliveryClient Response', response)); 96 | 97 | ``` 98 | 99 | ## Extending 100 | 101 | If you need to customize the Kentico Kontent Delivery SDK by registering interceptors and changing global config, you have to create a nuxt plugin. 102 | 103 | ### nuxt.config.js 104 | ``` json 105 | { 106 | modules: [ 107 | 'kentico-kontent-nuxt-module', 108 | ], 109 | 110 | plugins: [ 111 | '~/plugins/kenticokontentNuxtModule' 112 | ] 113 | } 114 | ``` 115 | 116 | ### plugins/kenticokontentNuxtModule.js 117 | 118 | ``` javascript 119 | export default function ({ store, $nuxtDeliveryClient }) { 120 | $nuxtDeliveryClient.config.globalHeaders = (queryConfig) => { 121 | let headers = []; 122 | headers.push({header: 'Authorization', value: 'bearer ' + store.state.token }); 123 | return headers; 124 | } 125 | } 126 | ``` 127 | 128 | ### Type Resolvers 129 | 130 | Type resolvers can also be registered by using a nuxt plugin: 131 | 132 | ### plugins/kenticokontentNuxtModule.js 133 | 134 | ``` javascript 135 | import { TypeResolver, ContentItem } from '@kentico/kontent-delivery'; 136 | 137 | class Page extends ContentItem { 138 | constructor() { 139 | super({ 140 | richTextResolver: (item, context) => { 141 | // todo: implement 142 | }, 143 | urlSlugResolver: (link, context) => { 144 | // todo: implement 145 | } 146 | }); 147 | } 148 | } 149 | 150 | export default function ({ store, app, $nuxtDeliveryClient }) { 151 | $nuxtDeliveryClient.config.typeResolvers = [ 152 | new TypeResolver('page', () => new Page()) 153 | ] 154 | } 155 | ``` -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', { 5 | targets: { 6 | esmodules: true 7 | } 8 | } 9 | ] 10 | ] 11 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils' 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kentico-kontent-nuxt-module", 3 | "version": "1.0.0-release", 4 | "description": "Add Kentico Kontent super power to your nuxt app", 5 | "keywords": [ 6 | "nuxt", 7 | "kentico", 8 | "kontent", 9 | "vuejs", 10 | "nuxtjs" 11 | ], 12 | "homepage": "https://github.com/Domitnator/kentico-kontent-nuxt-module#readme", 13 | "bugs": { 14 | "url": "https://github.com/Domitnator/kentico-kontent-nuxt-module/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/Domitnator/kentico-kontent-nuxt-module.git" 19 | }, 20 | "license": "ISC", 21 | "author": "Alfred Brockotter", 22 | "contributors": [ 23 | { 24 | "name": "Alfred Brockotter " 25 | } 26 | ], 27 | "main": "dist/index.js", 28 | "types": "dist/index.d.ts", 29 | "files": [ 30 | "dist", 31 | "templates" 32 | ], 33 | "scripts": { 34 | "build": "siroc build && mkdist --src src/runtime --dist dist/runtime --ext=js", 35 | "lint": "eslint --ext .js,.vue,.ts .", 36 | "semantic-release": "semantic-release", 37 | "test": "yarn lint && jest" 38 | }, 39 | "dependencies": { 40 | "@kentico/kontent-delivery": "^11.0.0", 41 | "chalk": "^4.1.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "latest", 45 | "@babel/plugin-transform-runtime": "latest", 46 | "@babel/preset-env": "latest", 47 | "@babel/preset-typescript": "latest", 48 | "@cld-apis/types": "^0.1.3", 49 | "@nuxt/test-utils": "latest", 50 | "@nuxt/types": "latest", 51 | "@nuxt/typescript-build": "^2.0.5", 52 | "@nuxt/typescript-runtime": "latest", 53 | "@nuxtjs/eslint-config-typescript": "latest", 54 | "@semantic-release/npm": "8.0.3", 55 | "@types/jest": "latest", 56 | "eslint": "latest", 57 | "jest": "latest", 58 | "nuxt": "latest", 59 | "semantic-release": "^18.0.0", 60 | "siroc": "latest", 61 | "standard-version": "latest" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import defu from 'defu' 3 | import chalk from 'chalk' 4 | import { resolve } from 'path' 5 | import { Module, Plugin } from '@nuxt/types' 6 | import { IDeliveryClientConfig } from '@kentico/kontent-delivery' 7 | import { logger } from './utilties/logger' 8 | import { INuxtDeliveryClient } from "./runtime/inuxt-delivery-client-interface"; 9 | 10 | type Exclude = T extends U ? never : T 11 | 12 | type RequireOne = { 13 | [X in Exclude]?: T[X] 14 | } & { 15 | [P in K]-?: T[P] 16 | } 17 | 18 | export interface ModuleOptions extends RequireOne{ 19 | } 20 | 21 | const deliveryClientModule: Module = function (moduleOptions) { 22 | const options = defu(this.options.kenticokontent, moduleOptions) 23 | 24 | if (!options.projectId) { 25 | logger.error(`You need to provide ${chalk.yellow('projectId')} to set up Kentico Kontent. See 👉 https://github.com/Domitnator/kentico-kontent-nuxt-module for more info.`) 26 | return 27 | } 28 | 29 | const runtimeDir = resolve(__dirname, 'runtime') 30 | this.nuxt.options.alias['~deliveryclientruntime'] = runtimeDir 31 | this.nuxt.options.build.transpile.push(runtimeDir, 'kentico-kontent-nuxt-module') 32 | 33 | // Add configuration plugin 34 | this.addPlugin({ 35 | src: resolve(__dirname, './runtime/kenticokontent-config.js'), 36 | fileName: 'deliveryclient/kenticokontent-config.js', 37 | options 38 | }) 39 | 40 | // Add plugin 41 | this.addPlugin({ 42 | src: resolve(__dirname, './runtime/plugin.template.js'), 43 | fileName: 'deliveryclient/kentico-kontent-nuxt-module.js', 44 | options 45 | }) 46 | } 47 | 48 | declare module '@nuxt/types' { 49 | interface Context { 50 | $nuxtDeliveryClient: INuxtDeliveryClient 51 | } 52 | 53 | interface NuxtAppOptions { 54 | $nuxtDeliveryClient: INuxtDeliveryClient 55 | } 56 | 57 | interface Configuration { 58 | nuxtDeliveryClient?: IDeliveryClientConfig 59 | } 60 | } 61 | 62 | declare module 'vue/types/vue' { 63 | interface Vue { 64 | $nuxtDeliveryClient: INuxtDeliveryClient; 65 | } 66 | } 67 | 68 | export default deliveryClientModule 69 | export const meta = require('../package.json') 70 | 71 | -------------------------------------------------------------------------------- /src/runtime/cache-service.ts: -------------------------------------------------------------------------------- 1 | import { Contracts, IContentItem, IContentItemElements, IDeliveryClient, IDeliveryNetworkResponse, MultipleItemsQuery, Responses } from "@kentico/kontent-delivery" 2 | 3 | export class CacheService { 4 | cacheEntries: any[] 5 | deliveryClient: IDeliveryClient; 6 | constructor (client: IDeliveryClient) { 7 | this.cacheEntries = []; 8 | this.deliveryClient = client; 9 | } 10 | viaCache>(query: MultipleItemsQuery, seconds: number, cacheKey?: string, isServerProcess?: boolean): Promise, Contracts.IListContentItemsContract>> { 11 | 12 | // Always perform a query when on the server 13 | if (this.cacheEntries && query && isServerProcess) { 14 | return query.toPromise(); 15 | }; 16 | 17 | if (!seconds) { 18 | seconds = 30 19 | } 20 | 21 | // If on client, check if a cache entry it present 22 | if (query && !process.server) { 23 | const key = (cacheKey && cacheKey !== '') ? cacheKey : query.getUrl() 24 | const cacheEntry = getCacheEntry(this.cacheEntries, key) 25 | 26 | // return from cache 27 | if (cacheEntry && getDiffInSeconds(new Date(), cacheEntry.timestamp) < seconds) { 28 | return new Promise, Contracts.IListContentItemsContract>>((resolve, reject) => { 29 | console.log('viaCache: response taken from cache'); 30 | resolve(cacheEntry.object); 31 | }); 32 | } else { 33 | return query.toPromise().then(response => { 34 | console.log('viaCache: response taken from server'); 35 | 36 | if (cacheEntry) { 37 | cacheEntry.timestamp = new Date() 38 | } else { 39 | this.cacheEntries.push({ key: key, object: response, seconds: seconds, timestamp: new Date() }) 40 | } 41 | return response 42 | }) 43 | } 44 | } 45 | 46 | return new Promise, Contracts.IListContentItemsContract>>((resolve, reject) => { 47 | resolve(null); 48 | }); 49 | 50 | } 51 | } 52 | 53 | function getCacheEntry(cacheEntries: any[], key: string) { 54 | if (cacheEntries) { 55 | return cacheEntries.find(s => s.key === key) 56 | } 57 | return null 58 | } 59 | function getDiffInSeconds(t1: Date, t2: any) { 60 | const dif = t1.getTime() - t2.getTime() 61 | const secondsFromT1ToT2 = dif / 1000 62 | const secondsBetweenDates = Math.abs(secondsFromT1ToT2) 63 | return secondsBetweenDates 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/runtime/inuxt-delivery-client-interface.ts: -------------------------------------------------------------------------------- 1 | import { Contracts, IContentItem, IContentItemElements, IDeliveryClient, IDeliveryNetworkResponse, MultipleItemsQuery, Responses} from '@kentico/kontent-delivery' 2 | 3 | export interface INuxtDeliveryClient extends IDeliveryClient { 4 | viaCache(query: MultipleItemsQuery, seconds: number, cacheKey?: string, isServerProcess?: boolean): 5 | Promise, Contracts.IListContentItemsContract>>; 6 | } -------------------------------------------------------------------------------- /src/runtime/kenticokontent-config.js: -------------------------------------------------------------------------------- 1 | const config = <%= JSON.stringify({kenticokontent: (options || {})}) %> 2 | export default config -------------------------------------------------------------------------------- /src/runtime/plugin.template.ts: -------------------------------------------------------------------------------- 1 | import { DeliveryClient, IContentItem, MultipleItemsQuery } from '@kentico/kontent-delivery'; 2 | import { Plugin } from '@nuxt/types' 3 | import { INuxtDeliveryClient } from "~deliveryclientruntime/inuxt-delivery-client-interface"; 4 | import { CacheService } from "~deliveryclientruntime/cache-service" 5 | 6 | // Default configuration 7 | let config = { 8 | kenticokontent: { 9 | projectId: '', 10 | globalQueryConfig: {} 11 | } 12 | } 13 | 14 | try { 15 | // tslint:disable-next-line: no-var-requires 16 | config = require('./kenticokontent-config.js') 17 | // @ts-ignore 18 | config = config.default || config 19 | // tslint:disable-next-line: no-empty 20 | } catch (error) {} 21 | 22 | declare module 'vue/types/vue' { 23 | interface Vue { 24 | $nuxtDeliveryClient: INuxtDeliveryClient 25 | } 26 | } 27 | 28 | declare module '@nuxt/types' { 29 | interface NuxtAppOptions { 30 | $nuxtDeliveryClient: INuxtDeliveryClient 31 | } 32 | interface Context { 33 | $nuxtDeliveryClient: INuxtDeliveryClient 34 | } 35 | } 36 | 37 | declare module 'vuex/types/index' { 38 | interface Store { 39 | $nuxtDeliveryClient: INuxtDeliveryClient 40 | } 41 | } 42 | 43 | const deliveryClientPlugin: Plugin = (context, inject) => { 44 | var kcSourceHeader = { header: 'X-KC-SOURCE', value: 'kentico-kontent-nuxt-module' }; 45 | 46 | if(config.kenticokontent.globalQueryConfig){ 47 | config.kenticokontent.globalQueryConfig = Object.assign({}, config.kenticokontent.globalQueryConfig, { 48 | customHeaders: [ 49 | kcSourceHeader 50 | ] 51 | }); 52 | } 53 | else{ 54 | config.kenticokontent = Object.assign({}, config.kenticokontent, { 55 | globalQueryConfig: { 56 | customHeaders: [ 57 | kcSourceHeader 58 | ] 59 | } 60 | }); 61 | } 62 | 63 | const deliveryClient = new DeliveryClient(config.kenticokontent); 64 | const cacheService = new CacheService(deliveryClient); 65 | 66 | const nuxtDeliveryClient = deliveryClient as any as INuxtDeliveryClient; 67 | nuxtDeliveryClient.viaCache = (query: MultipleItemsQuery, seconds: number, cacheKey?: string, isServerProcess?: boolean) => cacheService.viaCache(query, seconds, cacheKey, isServerProcess); 68 | 69 | inject('nuxtDeliveryClient', nuxtDeliveryClient) 70 | } 71 | 72 | export default deliveryClientPlugin -------------------------------------------------------------------------------- /src/utilties/logger.ts: -------------------------------------------------------------------------------- 1 | const consola = require('consola') 2 | export const logger = consola.withScope('nuxt:deliveryclient') -------------------------------------------------------------------------------- /tests/module.test.js: -------------------------------------------------------------------------------- 1 | // import { setupTest, get } from '@nuxt/test-utils' 2 | 3 | // describe('module', () => { 4 | // setupTest({ 5 | // }) 6 | 7 | // test('/ (pink)', async () => { 8 | // var test = true; 9 | 10 | // expect(test).toBe(true); 11 | // }) 12 | 13 | // }) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "esnext", 5 | "lib": [ 6 | "esnext", 7 | "dom" 8 | ], 9 | "esModuleInterop": true, 10 | "moduleResolution": "node", 11 | "declaration": false, 12 | "skipLibCheck": true, 13 | "resolveJsonModule": true, 14 | "allowSyntheticDefaultImports": true, 15 | "types": [ 16 | "node", 17 | "jest", 18 | "@nuxt/types" 19 | ] 20 | }, 21 | "exclude": [ 22 | "node_modules", 23 | "dist" 24 | ] 25 | } --------------------------------------------------------------------------------