├── .npmrc ├── docs ├── .npmrc ├── pages │ ├── [...slug].vue │ └── index.vue ├── server │ └── tsconfig.json ├── public │ └── favicon.ico ├── tsconfig.json ├── .gitignore ├── layouts │ └── docs.vue ├── package.json ├── components │ ├── Footer.vue │ └── Header.vue ├── nuxt.config.ts ├── app.vue ├── README.md ├── app.config.ts └── content │ └── index.yml ├── .eslintignore ├── src ├── helplers │ ├── index.ts │ └── scan-folder.ts ├── runtime │ └── server │ │ ├── handlers │ │ ├── index.ts │ │ ├── defineQueue.ts │ │ ├── defineCron.ts │ │ └── defineWorker.ts │ │ ├── routes │ │ └── ui-handler.ts │ │ └── utils │ │ └── concierge.ts ├── module.ts └── templates.ts ├── tsconfig.json ├── pnpm-workspace.yaml ├── playground ├── tsconfig.json ├── server │ ├── tsconfig.json │ ├── concierge │ │ ├── workers │ │ │ └── my-worker.ts │ │ ├── queues │ │ │ └── my-queue.ts │ │ └── cron │ │ │ └── ping.cron.ts │ └── api │ │ └── get-queue.ts ├── app.vue ├── nuxt.config.ts └── package.json ├── test ├── fixtures │ └── basic │ │ ├── package.json │ │ ├── app.vue │ │ └── nuxt.config.ts └── basic.test.ts ├── .eslintrc ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── CHANGELOG.md /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/helplers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./scan-folder"; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | } -------------------------------------------------------------------------------- /docs/pages/[...slug].vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "docs" 3 | - "playground" 4 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /docs/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /playground/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genu/nuxt-concierge/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "basic", 4 | "type": "module" 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/basic/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/runtime/server/handlers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./defineCron"; 2 | export * from "./defineQueue"; 3 | export * from "./defineWorker"; 4 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["@nuxt/eslint-config"], 4 | "rules": { 5 | "vue/multi-word-component-names": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/basic/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import MyModule from '../../../src/module' 2 | 3 | export default defineNuxtConfig({ 4 | modules: [ 5 | MyModule 6 | ] 7 | }) 8 | -------------------------------------------------------------------------------- /playground/server/concierge/workers/my-worker.ts: -------------------------------------------------------------------------------- 1 | import { defineWorker } from "#concierge-handlers"; 2 | 3 | export default defineWorker("SendEmail", async (job) => { 4 | // Do something 5 | }); 6 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | 4 | describe('ssr', async () => { 5 | 6 | 7 | it('renders the index page', async () => { 8 | 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /playground/server/concierge/queues/my-queue.ts: -------------------------------------------------------------------------------- 1 | import { defineQueue } from "#concierge-handlers"; 2 | 3 | export default defineQueue("SendEmail", { 4 | defaultJobOptions: { 5 | removeOnComplete: true, 6 | removeOnFail: true, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: ["../src/module"], 3 | concierge: { 4 | managementUI: true, 5 | }, 6 | imports: { 7 | autoImport: false, 8 | }, 9 | devtools: { enabled: true }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/runtime/server/handlers/defineQueue.ts: -------------------------------------------------------------------------------- 1 | import type { QueueOptions } from "bullmq"; 2 | 3 | export const defineQueue = ( 4 | name: string, 5 | opts?: Omit 6 | ) => { 7 | return { 8 | name, 9 | opts, 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /playground/server/concierge/cron/ping.cron.ts: -------------------------------------------------------------------------------- 1 | import { defineCron } from "#concierge-handlers"; 2 | 3 | export default defineCron( 4 | "PingServer", 5 | async () => { 6 | console.log("Ping!"); 7 | return "Pong"; 8 | }, 9 | { 10 | every: 5000, // 5 seconds 11 | } 12 | ); 13 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /playground/server/api/get-queue.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler } from "h3"; 2 | import { $useConcierge } from "#concierge"; 3 | 4 | export default defineEventHandler(async () => { 5 | const { getQueue } = $useConcierge(); 6 | const q = getQueue("myQueue"); 7 | 8 | await q.add("myJob", { foo: "bar" }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/runtime/server/handlers/defineCron.ts: -------------------------------------------------------------------------------- 1 | import type { Job, RepeatOptions } from "bullmq"; 2 | 3 | export const defineCron = ( 4 | name: string, 5 | processor: (job: Job) => Promise, 6 | schedule: RepeatOptions 7 | ) => { 8 | return { 9 | name, 10 | processor, 11 | schedule, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "my-module-playground", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate", 9 | "preview": "nuxi preview" 10 | }, 11 | "devDependencies": { 12 | "nuxt": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/runtime/server/handlers/defineWorker.ts: -------------------------------------------------------------------------------- 1 | import type { Processor, WorkerOptions } from "bullmq"; 2 | 3 | export const defineWorker = ( 4 | name: string, 5 | processor?: string | URL | null | Processor, 6 | opts?: Omit 7 | ) => { 8 | return { 9 | name, 10 | processor, 11 | opts, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /docs/layouts/docs.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /src/helplers/scan-folder.ts: -------------------------------------------------------------------------------- 1 | import fg from "fast-glob"; 2 | import { createResolver, useNuxt } from "@nuxt/kit"; 3 | 4 | export const scanFolder = async (path: string): Promise => { 5 | const nuxt = useNuxt(); 6 | const { resolve } = createResolver(import.meta.url); 7 | const resolvedPath = resolve(nuxt.options.srcDir, path); 8 | 9 | const files: string[] = []; 10 | 11 | const updatedFiles = await fg("**/*.{ts,js,mjs}", { 12 | cwd: resolvedPath, 13 | absolute: true, 14 | onlyFiles: true, 15 | }); 16 | 17 | files.push(...new Set(updatedFiles)); 18 | 19 | return files; 20 | }; 21 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-bullmq-docs", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "postinstall": "nuxt prepare" 11 | }, 12 | "devDependencies": { 13 | "nuxt": "^3.9.1", 14 | "vue": "^3.4.5", 15 | "vue-router": "^4.2.5" 16 | }, 17 | "dependencies": { 18 | "@iconify-json/heroicons": "^1.1.19", 19 | "@nuxt/content": "^2.10.0", 20 | "@nuxt/ui-pro": "^0.6.1", 21 | "@nuxtjs/google-fonts": "^3.1.3", 22 | "add": "^2.0.6" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | modules: ["@nuxt/content", "@nuxt/ui", "@nuxtjs/google-fonts"], 4 | extends: ["@nuxt/ui-pro"], 5 | devtools: { enabled: true }, 6 | hooks: { 7 | // Define `@nuxt/ui` components as global to use them in `.md` (feel free to add those you need) 8 | "components:extend": (components) => { 9 | const globals = components.filter((c) => 10 | ["UButton"].includes(c.pascalName) 11 | ); 12 | 13 | globals.forEach((c) => (c.global = true)); 14 | }, 15 | }, 16 | // googleFonts: { 17 | // display: "swap", 18 | // download: true, 19 | // families: { 20 | // "DM+Sans": [400, 500, 600, 700], 21 | // }, 22 | // }, 23 | }); 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .data 23 | .vercel_build_output 24 | .build-* 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /docs/app.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 40 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 Minimal Starter 2 | 3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install the dependencies: 8 | 9 | ```bash 10 | # npm 11 | npm install 12 | 13 | # pnpm 14 | pnpm install 15 | 16 | # yarn 17 | yarn install 18 | 19 | # bun 20 | bun install 21 | ``` 22 | 23 | ## Development Server 24 | 25 | Start the development server on `http://localhost:3000`: 26 | 27 | ```bash 28 | # npm 29 | npm run dev 30 | 31 | # pnpm 32 | pnpm run dev 33 | 34 | # yarn 35 | yarn dev 36 | 37 | # bun 38 | bun run dev 39 | ``` 40 | 41 | ## Production 42 | 43 | Build the application for production: 44 | 45 | ```bash 46 | # npm 47 | npm run build 48 | 49 | # pnpm 50 | pnpm run build 51 | 52 | # yarn 53 | yarn build 54 | 55 | # bun 56 | bun run build 57 | ``` 58 | 59 | Locally preview production build: 60 | 61 | ```bash 62 | # npm 63 | npm run preview 64 | 65 | # pnpm 66 | pnpm run preview 67 | 68 | # yarn 69 | yarn preview 70 | 71 | # bun 72 | bun run preview 73 | ``` 74 | 75 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 76 | -------------------------------------------------------------------------------- /src/runtime/server/routes/ui-handler.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, setResponseStatus } from "h3"; 2 | import { createBullBoard } from "@bull-board/api"; 3 | import { BullMQAdapter } from "@bull-board/api/bullMQAdapter"; 4 | import { H3Adapter } from "@bull-board/h3"; 5 | import { useRuntimeConfig } from "#imports"; 6 | import { $useConcierge } from "#concierge"; 7 | import { consola } from "consola"; 8 | import { resolvePath } from "mlly"; 9 | import { dirname } from "pathe"; 10 | 11 | const serverAdapter = new H3Adapter(); 12 | serverAdapter.setBasePath("/_concierge"); 13 | 14 | export default defineEventHandler(async (event) => { 15 | const { 16 | concierge: { ui, managementUI }, 17 | } = useRuntimeConfig(); 18 | 19 | const logger = consola.create({}).withTag("nuxt-concierge"); 20 | const uiPath = dirname( 21 | await resolvePath("@bull-board/ui/package.json", { 22 | url: import.meta.url, 23 | }) 24 | ); 25 | 26 | if (!managementUI) { 27 | logger.warn("Concierge is disabled"); 28 | setResponseStatus(event, 404); 29 | 30 | return ""; 31 | } 32 | 33 | const { queues } = $useConcierge(); 34 | 35 | createBullBoard({ 36 | queues: queues.map((queue) => new BullMQAdapter(queue)), 37 | serverAdapter, 38 | options: { 39 | uiBasePath: uiPath, 40 | uiConfig: ui, 41 | }, 42 | }); 43 | 44 | const uiRouter = serverAdapter.registerHandlers(); 45 | 46 | return uiRouter.handler(event); 47 | }); 48 | -------------------------------------------------------------------------------- /docs/components/Header.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-concierge", 3 | "version": "1.0.60", 4 | "description": "Task queue for Nuxt.js", 5 | "repository": "https://github.com/genu/nuxt-concierge", 6 | "license": "MIT", 7 | "type": "module", 8 | "configKey": "concierge", 9 | "compatibility": { 10 | "nuxt": "^2.9.0 || ^3.0.0-rc.1" 11 | }, 12 | "exports": { 13 | ".": { 14 | "types": "./dist/types.d.ts", 15 | "import": "./dist/module.mjs", 16 | "require": "./dist/module.cjs" 17 | } 18 | }, 19 | "main": "./dist/module.cjs", 20 | "types": "./dist/types.d.ts", 21 | "files": [ 22 | "dist" 23 | ], 24 | "scripts": { 25 | "prepack": "nuxt-module-build build", 26 | "dev": "nuxi dev playground", 27 | "dev:build": "nuxi build playground", 28 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 29 | "docs:build": "nuxi generate docs", 30 | "docs:preview": "nuxi preview docs", 31 | "docs:dev": "nuxi dev docs", 32 | "release": "pnpm run lint && pnpm run test && pnpm run prepack && changelogen --release && pnpm publish --access=public && git push --follow-tags", 33 | "lint": "eslint .", 34 | "test": "vitest run", 35 | "test:watch": "vitest watch" 36 | }, 37 | "dependencies": { 38 | "@bull-board/api": "^5.14.0", 39 | "@bull-board/h3": "^5.14.0", 40 | "@bull-board/ui": "^5.14.0", 41 | "@nuxt/kit": "^3.10.0", 42 | "bullmq": "^5.1.8", 43 | "colorette": "^2.0.20", 44 | "consola": "^3.2.3", 45 | "defu": "^6.1.4", 46 | "fast-glob": "^3.3.2", 47 | "ioredis": "^5.3.2", 48 | "pluralize": "^8.0.0", 49 | "ufo": "^1.3.2" 50 | }, 51 | "devDependencies": { 52 | "@nuxt/devtools": "latest", 53 | "@nuxt/eslint-config": "^0.2.0", 54 | "@nuxt/module-builder": "^0.5.5", 55 | "@nuxt/schema": "^3.10.0", 56 | "@nuxt/test-utils": "^3.11.0", 57 | "@types/node": "^20.11.16", 58 | "@types/pluralize": "^0.0.33", 59 | "changelogen": "^0.5.5", 60 | "eslint": "^8.56.0", 61 | "nuxt": "^3.10.0", 62 | "vitest": "^1.2.2" 63 | } 64 | } -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | ui: { 3 | primary: "green", 4 | gray: "slate", 5 | footer: { 6 | bottom: { 7 | left: "text-sm text-gray-500 dark:text-gray-400", 8 | wrapper: "border-t border-gray-200 dark:border-gray-800", 9 | }, 10 | }, 11 | }, 12 | seo: { 13 | siteName: "Nuxt UI Pro - Docs template", 14 | }, 15 | header: { 16 | logo: { 17 | alt: "", 18 | light: "", 19 | dark: "", 20 | }, 21 | search: true, 22 | colorMode: true, 23 | links: [ 24 | { 25 | icon: "i-simple-icons-github", 26 | to: "https://github.com/nuxt-ui-pro/docs", 27 | target: "_blank", 28 | "aria-label": "Docs template on GitHub", 29 | }, 30 | ], 31 | }, 32 | footer: { 33 | credits: "Copyright © 2023", 34 | colorMode: false, 35 | links: [ 36 | { 37 | icon: "i-simple-icons-nuxtdotjs", 38 | to: "https://nuxt.com", 39 | target: "_blank", 40 | "aria-label": "Nuxt Website", 41 | }, 42 | { 43 | icon: "i-simple-icons-discord", 44 | to: "https://discord.com/invite/ps2h6QT", 45 | target: "_blank", 46 | "aria-label": "Nuxt UI on Discord", 47 | }, 48 | { 49 | icon: "i-simple-icons-x", 50 | to: "https://x.com/nuxt_js", 51 | target: "_blank", 52 | "aria-label": "Nuxt on X", 53 | }, 54 | { 55 | icon: "i-simple-icons-github", 56 | to: "https://github.com/nuxt/ui", 57 | target: "_blank", 58 | "aria-label": "Nuxt UI on GitHub", 59 | }, 60 | ], 61 | }, 62 | toc: { 63 | title: "Table of Contents", 64 | bottom: { 65 | title: "Community", 66 | edit: "https://github.com/nuxt-ui-pro/docs/edit/main/content", 67 | links: [ 68 | { 69 | icon: "i-heroicons-star", 70 | label: "Star on GitHub", 71 | to: "https://github.com/nuxt/ui", 72 | target: "_blank", 73 | }, 74 | { 75 | icon: "i-heroicons-book-open", 76 | label: "Nuxt UI Pro docs", 77 | to: "https://ui.nuxt.com/pro/guide", 78 | target: "_blank", 79 | }, 80 | { 81 | icon: "i-simple-icons-nuxtdotjs", 82 | label: "Purchase a license", 83 | to: "https://ui.nuxt.com/pro/purchase", 84 | target: "_blank", 85 | }, 86 | ], 87 | }, 88 | }, 89 | }); 90 | -------------------------------------------------------------------------------- /docs/content/index.yml: -------------------------------------------------------------------------------- 1 | title: "Nuxt UI Pro - Docs template" 2 | description: "Welcome to Nuxt UI Pro documentation template." 3 | navigation: false 4 | hero: 5 | title: "Build your docs in seconds" 6 | description: "Create your documentation in seconds with this template!" 7 | orientation: horizontal 8 | headline: 9 | label: Made with Nuxt UI Pro 10 | to: https://ui.nuxt.com/pro 11 | icon: i-heroicons-arrow-top-right-on-square-20-solid 12 | links: 13 | - label: Get started 14 | icon: i-heroicons-arrow-right-20-solid 15 | trailing: true 16 | to: "/getting-started" 17 | size: lg 18 | - label: Use this template 19 | icon: i-simple-icons-github 20 | size: lg 21 | color: gray 22 | to: https://github.com/nuxt-ui-pro/docs 23 | target: _blank 24 | code: | 25 | ```bash [Terminal] 26 | npx nuxi init -t github:nuxt-ui-pro/docs 27 | ``` 28 | features: 29 | title: "All-in-one docs template" 30 | links: 31 | - label: "Explore Nuxt UI Pro" 32 | icon: "i-simple-icons-nuxtdotjs" 33 | trailingIcon: "i-heroicons-arrow-right-20-solid" 34 | color: "gray" 35 | to: "https://ui.nuxt.com/pro" 36 | target: "_blank" 37 | size: lg 38 | items: 39 | - title: "Nuxt 3" 40 | description: "Powered by Nuxt 3 for optimal performances and SEO." 41 | icon: "i-simple-icons-nuxtdotjs" 42 | to: "https://nuxt.com" 43 | target: "_blank" 44 | - title: "Markdown" 45 | description: "Write your pages with MDC thanks to Nuxt Content." 46 | icon: "i-simple-icons-markdown" 47 | to: "https://content.nuxt.com" 48 | target: "_blank" 49 | - title: "Nuxt UI" 50 | description: "Offers a very large set of full customizable components." 51 | icon: "i-heroicons-sparkles-20-solid" 52 | to: "https://ui.nuxt.com" 53 | target: "_blank" 54 | - title: "TypeScript" 55 | description: "A fully typed development experience." 56 | icon: "i-simple-icons-typescript" 57 | to: "https://www.typescriptlang.org" 58 | target: "_blank" 59 | - title: "Nuxt Studio" 60 | description: "Supported by Nuxt Studio for fast updates and previews." 61 | icon: "i-simple-icons-nuxtdotjs" 62 | to: "https://nuxt.studio" 63 | target: "_blank" 64 | - title: "Search" 65 | description: "A full-text search modal empowered by Fuse.js." 66 | icon: "i-heroicons-magnifying-glass-20-solid" 67 | to: "https://ui.nuxt.com/pro/components/docs/docs-search" 68 | target: "_blank" 69 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineNuxtModule, 3 | useLogger, 4 | createResolver, 5 | addServerPlugin, 6 | addServerHandler, 7 | } from "@nuxt/kit"; 8 | import pluralize from "pluralize"; 9 | import type { UIConfig } from "@bull-board/api/dist/typings/app"; 10 | import type { RedisOptions } from "bullmq"; 11 | import defu from "defu"; 12 | import { underline, yellow } from "colorette"; 13 | import { 14 | withTrailingSlash, 15 | withoutTrailingSlash, 16 | cleanDoubleSlashes, 17 | joinURL, 18 | } from "ufo"; 19 | import { name, version, configKey, compatibility } from "../package.json"; 20 | import { scanFolder } from "./helplers"; 21 | import { createTemplateNuxtPlugin, createTemplateType } from "./templates"; 22 | 23 | export interface ModuleOptions { 24 | redis: RedisOptions; 25 | ui: UIConfig; 26 | queues: string[]; 27 | managementUI?: boolean; 28 | } 29 | 30 | export default defineNuxtModule({ 31 | meta: { 32 | name, 33 | configKey, 34 | version, 35 | compatibility, 36 | }, 37 | defaults: { 38 | ui: { 39 | boardTitle: "Concierge", 40 | }, 41 | redis: { 42 | host: process.env.NUXT_REDIS_HOST, 43 | port: Number(process.env.NUXT_REDIS_PORT), 44 | password: process.env.NUXT_REDIS_PASSWORD, 45 | }, 46 | queues: [], 47 | managementUI: process.env.NODE_ENV === "development", 48 | }, 49 | async setup(options, nuxt) { 50 | const { resolve } = createResolver(import.meta.url); 51 | const logger = useLogger(name); 52 | 53 | // Add Server handlers for UI 54 | addServerHandler({ 55 | route: "/_concierge", 56 | handler: resolve("./runtime/server/routes/ui-handler"), 57 | }); 58 | 59 | addServerHandler({ 60 | route: "/_concierge/**", 61 | handler: resolve("./runtime/server/routes/ui-handler"), 62 | }); 63 | 64 | addServerPlugin(resolve(nuxt.options.buildDir, "0.concierge-nuxt-plugin")); 65 | 66 | const workers = await scanFolder("server/concierge/workers"); 67 | const queues = await scanFolder("server/concierge/queues"); 68 | const cronJobs = await scanFolder("server/concierge/cron"); 69 | 70 | createTemplateNuxtPlugin(queues, workers, cronJobs, options.queues, name); 71 | createTemplateType(); 72 | 73 | if (nuxt.options.dev) { 74 | logger.success( 75 | `Created ${pluralize("queue", queues.length, true)} and ${pluralize( 76 | "worker", 77 | workers.length, 78 | true 79 | )}` 80 | ); 81 | } 82 | 83 | // Transpile BullBoard api because its not ESM 84 | nuxt.options.build.transpile.push("@bull-board/api"); 85 | nuxt.options.build.transpile.push("@bull-board/h3"); 86 | nuxt.options.build.transpile.push("@bull-board/ui"); 87 | 88 | nuxt.options.runtimeConfig.concierge = defu( 89 | nuxt.options.runtimeConfig.concierge, 90 | options 91 | ); 92 | 93 | if (nuxt.options.dev) { 94 | const viewerUrl = `${cleanDoubleSlashes( 95 | joinURL(withoutTrailingSlash(nuxt.options.devServer.url), "_concierge") 96 | )}`; 97 | 98 | logger.info( 99 | `Concierge Dashboard: ${underline( 100 | yellow(withTrailingSlash(viewerUrl)) 101 | )}` 102 | ); 103 | } 104 | }, 105 | }); 106 | -------------------------------------------------------------------------------- /src/runtime/server/utils/concierge.ts: -------------------------------------------------------------------------------- 1 | import { Queue, Worker } from "bullmq"; 2 | import { consola } from "consola"; 3 | 4 | import type { 5 | WorkerOptions, 6 | ConnectionOptions, 7 | QueueOptions, 8 | RedisOptions, 9 | Processor, 10 | RepeatOptions, 11 | } from "bullmq"; 12 | import { useRuntimeConfig } from "#imports"; 13 | 14 | const logger = consola.create({}).withTag("nuxt-concierge"); 15 | 16 | const queues: Queue[] = []; 17 | const workers: Worker[] = []; 18 | const cronJobs: { 19 | name: string; 20 | processor: () => Promise; 21 | schedule: RepeatOptions; 22 | }[] = []; 23 | 24 | export const $useConcierge = () => { 25 | const { 26 | concierge: { 27 | redis: { host, password, port }, 28 | }, 29 | } = useRuntimeConfig(); 30 | 31 | const redisOptions: RedisOptions = { 32 | host, 33 | password, 34 | port, 35 | retryStrategy: function (times: number) { 36 | return Math.max(Math.min(Math.exp(times), 20000), 1000); 37 | }, 38 | }; 39 | 40 | const connectionOptions: ConnectionOptions = { 41 | host, 42 | password, 43 | port, 44 | }; 45 | 46 | const createQueue = ( 47 | name: string, 48 | opts?: Omit 49 | ) => { 50 | // check if queue already exists 51 | if (queues.find((queue) => queue.name === name)) { 52 | logger.warn(`Queue ${name} already exists`); 53 | return; 54 | } 55 | const defaultConnectionOptions: ConnectionOptions = { 56 | enableOfflineQueue: false, 57 | }; 58 | 59 | const queue = new Queue(name, { 60 | connection: { ...redisOptions, ...defaultConnectionOptions }, 61 | ...opts, 62 | }); 63 | 64 | queues.push(queue); 65 | 66 | return queue; 67 | }; 68 | 69 | const createWorker = ( 70 | name: string, 71 | processor?: string | URL | null | Processor, 72 | opts?: Omit 73 | ) => { 74 | const defaultConnectionOptions: ConnectionOptions = { 75 | enableOfflineQueue: true, 76 | maxRetriesPerRequest: null, 77 | }; 78 | 79 | const worker = new Worker(name, processor, { 80 | connection: { ...redisOptions, ...defaultConnectionOptions }, 81 | ...opts, 82 | }).on("closed", () => { 83 | logger.info(`Worker ${name} stopped`); 84 | }); 85 | 86 | workers.push(worker); 87 | 88 | return worker; 89 | }; 90 | 91 | const addCronJob = ( 92 | name: string, 93 | processor: () => Promise, 94 | schedule: RepeatOptions 95 | ) => { 96 | cronJobs.push({ 97 | name, 98 | processor, 99 | schedule, 100 | }); 101 | }; 102 | 103 | /** 104 | * Returns the a queue by name. If queue is not found, it will return undefined but log a warning. 105 | * 106 | * @param name Name of the queue 107 | */ 108 | const getQueue = (name: string) => { 109 | const queue = queues.find((queue) => queue.name === name)!; 110 | 111 | if (!queue) { 112 | logger.warn(`Queue ${name} not found`); 113 | } 114 | 115 | return queue; 116 | }; 117 | 118 | const getCronJob = (name: string) => { 119 | return cronJobs.find((cronJob) => cronJob.name === name); 120 | }; 121 | 122 | return { 123 | queues, 124 | workers, 125 | createQueue, 126 | createWorker, 127 | getQueue, 128 | addCronJob, 129 | getCronJob, 130 | connectionOptions, 131 | }; 132 | }; 133 | -------------------------------------------------------------------------------- /src/templates.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addTemplate, 3 | addTypeTemplate, 4 | createResolver, 5 | useNuxt, 6 | } from "@nuxt/kit"; 7 | 8 | const importFiles = (files: string[], prefix: string = "file") => 9 | files 10 | .map( 11 | (file, index) => 12 | `import ${prefix}${index} from '${file.replace(".ts", "")}'` 13 | ) 14 | .join("\n"); 15 | 16 | const methodFactory = ( 17 | input: any[], 18 | methodName: string, 19 | importedKey: string, 20 | importedVarProps: string[] 21 | ) => { 22 | const r = input.map((_item, index) => { 23 | const argsString: string[] = []; 24 | 25 | for (const arg of importedVarProps) { 26 | argsString.push(`${importedKey}${index}.${arg}`); 27 | } 28 | return `${methodName}(${argsString.join(",")});`; 29 | }); 30 | 31 | return r.join("\n\t\t"); 32 | }; 33 | 34 | export const createTemplateNuxtPlugin = ( 35 | queues: string[], 36 | workers: string[], 37 | cron: string[], 38 | adhocQueues: string[], 39 | moduleName: string 40 | ) => { 41 | const nitroPlugin = ` 42 | import { consola } from "consola"; 43 | import { defineNitroPlugin } from "#imports"; 44 | import { $useConcierge } from "#concierge"; 45 | ${importFiles(queues, "queue")} 46 | ${importFiles(workers, "worker")} 47 | ${importFiles(cron, "cron")} 48 | 49 | const cronWorkerProcessor = async (job) => { 50 | const { getCronJob } = $useConcierge(); 51 | const { name } = job.data; 52 | 53 | const cronJob = getCronJob(name); 54 | 55 | return await cronJob.processor(job); 56 | } 57 | 58 | export default defineNitroPlugin(async (nitroApp) => { 59 | const logger = consola.create({}).withTag("${moduleName}") 60 | const { workers, createQueue, createWorker, addCronJob } = $useConcierge(); 61 | 62 | // CRON Queue 63 | const cronQueue = createQueue("CRON"); 64 | 65 | // Remove old cron jobs 66 | await cronQueue.obliterate({force: true}) 67 | 68 | createWorker("CRON", cronWorkerProcessor) 69 | 70 | // Add CRON Jobs 71 | ${methodFactory(cron, "addCronJob", "cron", [ 72 | "name", 73 | "processor", 74 | "schedule", 75 | ])} 76 | 77 | ${cron.map((_cron, i) => { 78 | return ` 79 | cronQueue.add(cron${i}.name, { name: cron${i}.name }, { 80 | repeat: cron${0}.schedule 81 | }) 82 | `; 83 | })} 84 | 85 | // Queues 86 | ${methodFactory(queues, "createQueue", "queue", ["name", "opts"])} 87 | 88 | // Simple Queues 89 | ${adhocQueues.map((queue) => `createQueue("${queue}");`).join("\n\t\t")} 90 | 91 | // Workers 92 | ${methodFactory(workers, "createWorker", "worker", [ 93 | "name", 94 | "processor", 95 | "opts", 96 | ])} 97 | 98 | nitroApp.hooks.hookOnce("close", async () => { 99 | logger.info("Stopping " + workers.length + " workers"); 100 | 101 | await Promise.all(workers.map((worker) => worker.close())); 102 | }) 103 | }) 104 | `; 105 | 106 | addTemplate({ 107 | filename: "0.concierge-nuxt-plugin.ts", 108 | write: true, 109 | getContents: () => nitroPlugin, 110 | }); 111 | }; 112 | 113 | export const createTemplateType = () => { 114 | const { resolve } = createResolver(import.meta.url); 115 | const nuxt = useNuxt(); 116 | 117 | nuxt.hook("nitro:config", (nitroConfig) => { 118 | if (!nitroConfig.alias) return; 119 | 120 | nitroConfig.alias["#concierge"] = resolve( 121 | "./runtime/server/utils/concierge" 122 | ); 123 | 124 | nitroConfig.alias["#concierge-handlers"] = resolve( 125 | "./runtime/server/handlers" 126 | ); 127 | }); 128 | 129 | addTypeTemplate({ 130 | filename: "types/concierge-handlers.d.ts", 131 | write: true, 132 | getContents() { 133 | return ` 134 | declare module "#concierge-handlers" { 135 | const defineQueue: typeof import("${resolve( 136 | "./runtime/server/handlers/defineQueue" 137 | )}").defineQueue; 138 | const defineWorker: typeof import("${resolve( 139 | "./runtime/server/handlers/defineWorker" 140 | )}").defineWorker; 141 | const defineCron: typeof import("${resolve( 142 | "./runtime/server/handlers/defineCron" 143 | )}").defineCron; 144 | } 145 | `; 146 | }, 147 | }); 148 | 149 | addTypeTemplate({ 150 | filename: "types/concierge.d.ts", 151 | write: true, 152 | getContents() { 153 | return ` 154 | declare module "#concierge" { 155 | const $useConcierge: typeof import("${resolve( 156 | "./runtime/server/utils/concierge" 157 | )}").$useConcierge; 158 | } 159 | `; 160 | }, 161 | }); 162 | }; 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-concierge 2 | 3 | [![npm version](https://badge.fury.io/js/nuxt-concierge.svg)](https://www.npmjs.com/package/nuxt-concierge) 4 | [![npm downloads](https://img.shields.io/npm/dm/nuxt-concierge.svg)](https://www.npmjs.com/package/nuxt-concierge) 5 | [![License](https://img.shields.io/npm/l/nuxt-concierge.svg)](https://github.com/your-org/nuxt-concierge/blob/main/LICENSE) 6 | [![Nuxt](https://img.shields.io/badge/nuxt.js-v2.14.12-04C690.svg)](https://nuxtjs.org/) 7 | 8 | Queues, workers and background jobs for nuxt 9 | 10 | - [✨  Release Notes](/CHANGELOG.md) 11 | 12 | ## Features 13 | 14 | - Create queues and workers based on bullmq 15 | - Create scheduled tasks 16 | - UI for managing jobs 17 | - Autoscan and initialize queues automatically 18 | - Dynamically create new queues and workers 19 | - Ability to integrate with external queues and workers 20 | 21 | ## Quick Setup 22 | 23 | 1. Add `nuxt-concierge` dependency to your project 24 | 25 | ```bash 26 | # Using pnpm 27 | pnpm add -D nuxt-concierge 28 | 29 | # Using yarn 30 | yarn add --dev nuxt-concierge 31 | 32 | # Using npm 33 | npm install --save-dev nuxt-concierge 34 | ``` 35 | 36 | 2. Add `nuxt-concierge` to the `modules` section of `nuxt.config.ts` 37 | 38 | ```js 39 | export default defineNuxtConfig({ 40 | modules: ["nuxt-concierge"], 41 | concierge: { 42 | redis: { 43 | host: "localhost", 44 | port: 6379, 45 | password: "", 46 | }, 47 | }, 48 | }); 49 | ``` 50 | 51 | Use the `redis` option to configure the redis connection, or use the `redis` environment variables: 52 | 53 | ```bash 54 | 55 | NUXT_REDIS_HOST='localhost' 56 | NUXT_REDIS_PORT=6379 57 | NUXT_REDIS_PASSWORD='' 58 | ``` 59 | 60 | **Note**: A redis connetion **is required** for this module to work. 61 | 62 | ## Usage 63 | 64 | ### Creating Queues 65 | 66 | There are two ways to create queues. 67 | 68 | 1. Inline using options 69 | 70 | We call this **Simple Queues** because only a queue name is needed. To create a simple queue: 71 | 72 | ```ts 73 | concierge: { 74 | queues: ["SendEmail"], 75 | }, 76 | 77 | ``` 78 | 79 | 2. The second method for creating queues is by defining it using `defineQueue`. 80 | 81 | For example: 82 | 83 | `/concierge/queues/my-queue.ts`: 84 | 85 | ```ts 86 | import { defineQueue } from "#concierge-handlers"; 87 | 88 | export default defineQueue("SendEmail", { 89 | defaultJobOptions: { 90 | removeOnComplete: true, 91 | removeOnFail: true, 92 | }, 93 | }); 94 | ``` 95 | 96 | **NOTE**: your queue must be created in the `/concierge/queues` folder 97 | 98 | The second parameter gives you full control over the queue behavior 99 | 100 | ### Creating Workers 101 | 102 | For jobs to be processed at least one worker is required. Workers don't have to necessarily be running in a nuxt, but if you want to manage workers in nuxt, you can create one using `defineWorker` 103 | 104 | For example: 105 | 106 | `/concierge/workers/my-worker.ts`: 107 | 108 | ```ts 109 | import { defineWorker } from "#concierge-handlers"; 110 | 111 | export default defineWorker("SendEmail", async (job) => { 112 | const { to } = job.data; 113 | 114 | // send customer email 115 | }); 116 | ``` 117 | 118 | Workers defined this way will **automatically be started** during application startup 119 | 120 | **NOTE**: your workers must be created in the `/concierge/workers` folder 121 | 122 | ### Creating CRON Jobs 123 | 124 | Cron jobs can be created using the `defineCron` helper. 125 | 126 | For example: 127 | 128 | `/concierge/cron/daily-report.ts`: 129 | 130 | ```ts 131 | import { defineCron } from "#concierge-handlers"; 132 | 133 | export default defineCron( 134 | "DailySalesReport", 135 | async () => { 136 | // Run a daily report 137 | }, 138 | { 139 | every: 43200000, // 12 hours 140 | immediately: true, 141 | } 142 | ); 143 | ``` 144 | 145 | Cron jobs are placed in a special queue called `CRON`. This queue is automatically created for you. 146 | 147 | **NOTE**: your cron must be created in the `/concierge/cron` folder 148 | 149 | ### Accessing queues 150 | 151 | To access queues, a `getQueue` helper is provided. 152 | 153 | ```ts 154 | import { $useConcierge } from "#concierge"; 155 | 156 | export default defineEventHandler(async (event) => { 157 | const { getQueue } = $useConcierge(); 158 | const emailQueue = getQueue("SendEmail"); 159 | 160 | await emailQueue.add("sendWelcomeEmail", { to: "customer@helloworld.com" }); 161 | 162 | return true; 163 | }); 164 | ``` 165 | 166 | This would add a job to the queue and will be processed by the worker. 167 | 168 | ### Queue Management UI 169 | 170 | The Concierge dashboard can be accessed at: 171 | 172 | http://localhost:3000/\_concierge/ 173 | 174 | ## FAQ 175 | 176 | 1. **Does this work in a serverless environment?** 177 | 178 | Yes and no, but mostly no. Serverless environments typically have a timeout, so workers will not be able to run in the backgorund to reliably process jobs. If you want to use this module in a serverless environment, I recommend to deploy your workers elsewhere, and just reference the queues in this module. This way you can still leverage the other features, like queue management, and ui. 179 | 180 | 2. **Can I disable the Queue management UI in production?** 181 | 182 | The management UI is already disabled by default in production. If you want't to enable it in production, you must enable it explicitly in the options: 183 | 184 | ```ts 185 | export default defineNuxtConfig({ 186 | concierge: { 187 | redis: { 188 | host: "...", 189 | }, 190 | managementUI: true, 191 | }, 192 | }); 193 | ``` 194 | 195 | 3. **Can I password protect the Queue management UI?** 196 | 197 | Auth for the UI is out of scope of this module, but it can easily be done using the [Nuxt Security](https://nuxt-security.vercel.app/) 198 | 199 | ## Development 200 | 201 | ```bash 202 | # Install dependencies 203 | pnpm install 204 | 205 | # Generate type stubs 206 | pnpm dev:prepare 207 | 208 | # Develop with the playground 209 | pnpm dev 210 | 211 | # Build the playground 212 | pnpm dev:build 213 | 214 | # Run ESLint 215 | pnpm lint 216 | 217 | # Run Vitest 218 | pnpm test 219 | pnpm test:watch 220 | 221 | # Release new version 222 | pnpm release 223 | ``` 224 | 225 | 226 | 227 | [npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=18181B&colorB=28CF8D 228 | [npm-version-href]: https://npmjs.com/package/my-module 229 | [npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D 230 | [npm-downloads-href]: https://npmjs.com/package/my-module 231 | [license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D 232 | [license-href]: https://npmjs.com/package/my-module 233 | [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js 234 | [nuxt-href]: https://nuxt.com 235 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## v1.0.60 5 | 6 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.59...v1.0.60) 7 | 8 | ### 🏡 Chore 9 | 10 | - **release:** V1.0.59 ([7cb5b89](https://github.com/genu/nuxt-concierge/commit/7cb5b89)) 11 | 12 | ### ❤️ Contributors 13 | 14 | - Eugen Istoc 15 | 16 | ## v1.0.59 17 | 18 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.58...v1.0.59) 19 | 20 | ### 🏡 Chore 21 | 22 | - **release:** V1.0.58 ([2f38497](https://github.com/genu/nuxt-concierge/commit/2f38497)) 23 | 24 | ### ❤️ Contributors 25 | 26 | - Eugen Istoc 27 | 28 | ## v1.0.58 29 | 30 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.57...v1.0.58) 31 | 32 | ### 🏡 Chore 33 | 34 | - **release:** V1.0.57 ([da5f68c](https://github.com/genu/nuxt-concierge/commit/da5f68c)) 35 | 36 | ### ❤️ Contributors 37 | 38 | - Eugen Istoc 39 | 40 | ## v1.0.57 41 | 42 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.56...v1.0.57) 43 | 44 | ### 🏡 Chore 45 | 46 | - **release:** V1.0.56 ([75d5c1b](https://github.com/genu/nuxt-concierge/commit/75d5c1b)) 47 | 48 | ### ❤️ Contributors 49 | 50 | - Eugen Istoc 51 | 52 | ## v1.0.56 53 | 54 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.55...v1.0.56) 55 | 56 | ### 🏡 Chore 57 | 58 | - **release:** V1.0.55 ([0f6a558](https://github.com/genu/nuxt-concierge/commit/0f6a558)) 59 | 60 | ### ❤️ Contributors 61 | 62 | - Eugen Istoc 63 | 64 | ## v1.0.55 65 | 66 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.54...v1.0.55) 67 | 68 | ### 🏡 Chore 69 | 70 | - **release:** V1.0.54 ([a0bbd5e](https://github.com/genu/nuxt-concierge/commit/a0bbd5e)) 71 | 72 | ### ❤️ Contributors 73 | 74 | - Eugen Istoc 75 | 76 | ## v1.0.54 77 | 78 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.53...v1.0.54) 79 | 80 | ### 🏡 Chore 81 | 82 | - **release:** V1.0.53 ([718b915](https://github.com/genu/nuxt-concierge/commit/718b915)) 83 | 84 | ### ❤️ Contributors 85 | 86 | - Eugen Istoc 87 | 88 | ## v1.0.53 89 | 90 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.52...v1.0.53) 91 | 92 | ### 🏡 Chore 93 | 94 | - **release:** V1.0.52 ([49d6d19](https://github.com/genu/nuxt-concierge/commit/49d6d19)) 95 | 96 | ### ❤️ Contributors 97 | 98 | - Eugen Istoc 99 | 100 | ## v1.0.52 101 | 102 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.51...v1.0.52) 103 | 104 | ### 🏡 Chore 105 | 106 | - **release:** V1.0.51 ([1e3e296](https://github.com/genu/nuxt-concierge/commit/1e3e296)) 107 | 108 | ### ❤️ Contributors 109 | 110 | - Eugen Istoc 111 | 112 | ## v1.0.51 113 | 114 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.50...v1.0.51) 115 | 116 | ### 🏡 Chore 117 | 118 | - **release:** V1.0.50 ([e4ff793](https://github.com/genu/nuxt-concierge/commit/e4ff793)) 119 | 120 | ### ❤️ Contributors 121 | 122 | - Eugen Istoc 123 | 124 | ## v1.0.50 125 | 126 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.49...v1.0.50) 127 | 128 | ### 🏡 Chore 129 | 130 | - **release:** V1.0.49 ([28de658](https://github.com/genu/nuxt-concierge/commit/28de658)) 131 | 132 | ### ❤️ Contributors 133 | 134 | - Eugen Istoc 135 | 136 | ## v1.0.49 137 | 138 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.48...v1.0.49) 139 | 140 | ### 🏡 Chore 141 | 142 | - **release:** V1.0.48 ([460eb2d](https://github.com/genu/nuxt-concierge/commit/460eb2d)) 143 | 144 | ### ❤️ Contributors 145 | 146 | - Eugen Istoc 147 | 148 | ## v1.0.48 149 | 150 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.47...v1.0.48) 151 | 152 | ### 🏡 Chore 153 | 154 | - **release:** V1.0.47 ([33241ec](https://github.com/genu/nuxt-concierge/commit/33241ec)) 155 | 156 | ### ❤️ Contributors 157 | 158 | - Eugen Istoc 159 | 160 | ## v1.0.47 161 | 162 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.46...v1.0.47) 163 | 164 | ### 🏡 Chore 165 | 166 | - **release:** V1.0.46 ([8f63f03](https://github.com/genu/nuxt-concierge/commit/8f63f03)) 167 | 168 | ### ❤️ Contributors 169 | 170 | - Eugen Istoc 171 | 172 | ## v1.0.46 173 | 174 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.45...v1.0.46) 175 | 176 | ## v1.0.45 177 | 178 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.44...v1.0.45) 179 | 180 | ### 🏡 Chore 181 | 182 | - **release:** V1.0.44 ([0a2d662](https://github.com/genu/nuxt-concierge/commit/0a2d662)) 183 | 184 | ### ❤️ Contributors 185 | 186 | - Eugen Istoc 187 | 188 | ## v1.0.44 189 | 190 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.43...v1.0.44) 191 | 192 | ## v1.0.43 193 | 194 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.42...v1.0.43) 195 | 196 | ### 🏡 Chore 197 | 198 | - **release:** V1.0.42 ([bb7949b](https://github.com/genu/nuxt-concierge/commit/bb7949b)) 199 | 200 | ### ❤️ Contributors 201 | 202 | - Eugen Istoc 203 | 204 | ## v1.0.42 205 | 206 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.41...v1.0.42) 207 | 208 | ### 🏡 Chore 209 | 210 | - **release:** V1.0.41 ([cd62e61](https://github.com/genu/nuxt-concierge/commit/cd62e61)) 211 | 212 | ### ❤️ Contributors 213 | 214 | - Eugen Istoc 215 | 216 | ## v1.0.41 217 | 218 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.40...v1.0.41) 219 | 220 | ### 🏡 Chore 221 | 222 | - **release:** V1.0.40 ([dd45eea](https://github.com/genu/nuxt-concierge/commit/dd45eea)) 223 | 224 | ### ❤️ Contributors 225 | 226 | - Eugen Istoc 227 | 228 | ## v1.0.40 229 | 230 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.39...v1.0.40) 231 | 232 | ### 🏡 Chore 233 | 234 | - **release:** V1.0.39 ([6c72e0a](https://github.com/genu/nuxt-concierge/commit/6c72e0a)) 235 | 236 | ### ❤️ Contributors 237 | 238 | - Eugen Istoc 239 | 240 | ## v1.0.39 241 | 242 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.38...v1.0.39) 243 | 244 | ## v1.0.38 245 | 246 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.36...v1.0.38) 247 | 248 | ### 🏡 Chore 249 | 250 | - **release:** V1.0.36 ([eeb81d8](https://github.com/genu/nuxt-concierge/commit/eeb81d8)) 251 | - **release:** V1.0.37 ([95c93b2](https://github.com/genu/nuxt-concierge/commit/95c93b2)) 252 | 253 | ### ❤️ Contributors 254 | 255 | - Eugen Istoc 256 | 257 | ## v1.0.37 258 | 259 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.36...v1.0.37) 260 | 261 | ## v1.0.36 262 | 263 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.35...v1.0.36) 264 | 265 | ### 🏡 Chore 266 | 267 | - **release:** V1.0.35 ([bcf2971](https://github.com/genu/nuxt-concierge/commit/bcf2971)) 268 | 269 | ### ❤️ Contributors 270 | 271 | - Eugen Istoc 272 | 273 | ## v1.0.35 274 | 275 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.34...v1.0.35) 276 | 277 | ### 🏡 Chore 278 | 279 | - **release:** V1.0.34 ([a5c6055](https://github.com/genu/nuxt-concierge/commit/a5c6055)) 280 | 281 | ### ❤️ Contributors 282 | 283 | - Eugen Istoc 284 | 285 | ## v1.0.34 286 | 287 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.33...v1.0.34) 288 | 289 | ### 🏡 Chore 290 | 291 | - **release:** V1.0.33 ([ce8f5c5](https://github.com/genu/nuxt-concierge/commit/ce8f5c5)) 292 | 293 | ### ❤️ Contributors 294 | 295 | - Eugen Istoc 296 | 297 | ## v1.0.33 298 | 299 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.32...v1.0.33) 300 | 301 | ### 🏡 Chore 302 | 303 | - **release:** V1.0.32 ([10fa3f3](https://github.com/genu/nuxt-concierge/commit/10fa3f3)) 304 | 305 | ### ❤️ Contributors 306 | 307 | - Eugen Istoc 308 | 309 | ## v1.0.32 310 | 311 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.31...v1.0.32) 312 | 313 | ### 🏡 Chore 314 | 315 | - **release:** V1.0.31 ([cecbbec](https://github.com/genu/nuxt-concierge/commit/cecbbec)) 316 | 317 | ### ❤️ Contributors 318 | 319 | - Eugen Istoc 320 | 321 | ## v1.0.31 322 | 323 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.30...v1.0.31) 324 | 325 | ### 🏡 Chore 326 | 327 | - **release:** V1.0.30 ([12beaf7](https://github.com/genu/nuxt-concierge/commit/12beaf7)) 328 | 329 | ### ❤️ Contributors 330 | 331 | - Eugen Istoc 332 | 333 | ## v1.0.30 334 | 335 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.29...v1.0.30) 336 | 337 | ### 🏡 Chore 338 | 339 | - **release:** V1.0.29 ([81d9c7e](https://github.com/genu/nuxt-concierge/commit/81d9c7e)) 340 | 341 | ### ❤️ Contributors 342 | 343 | - Eugen Istoc 344 | 345 | ## v1.0.29 346 | 347 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.28...v1.0.29) 348 | 349 | ### 🏡 Chore 350 | 351 | - **release:** V1.0.28 ([f91c4da](https://github.com/genu/nuxt-concierge/commit/f91c4da)) 352 | 353 | ### ❤️ Contributors 354 | 355 | - Eugen Istoc 356 | 357 | ## v1.0.28 358 | 359 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.27...v1.0.28) 360 | 361 | ## v1.0.27 362 | 363 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.26...v1.0.27) 364 | 365 | ## v1.0.26 366 | 367 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.25...v1.0.26) 368 | 369 | ### 🏡 Chore 370 | 371 | - **release:** V1.0.25 ([01c71b2](https://github.com/genu/nuxt-concierge/commit/01c71b2)) 372 | 373 | ### ❤️ Contributors 374 | 375 | - Eugen Istoc 376 | 377 | ## v1.0.25 378 | 379 | 380 | ### 🏡 Chore 381 | 382 | - **release:** V1.0.1 ([c3c7c89](https://github.com/genu/nuxt-concierge/commit/c3c7c89)) 383 | - **release:** V1.0.2 ([7ea93ef](https://github.com/genu/nuxt-concierge/commit/7ea93ef)) 384 | - **release:** V1.0.3 ([b11f9f7](https://github.com/genu/nuxt-concierge/commit/b11f9f7)) 385 | - **release:** V1.0.4 ([84c5abf](https://github.com/genu/nuxt-concierge/commit/84c5abf)) 386 | - **release:** V1.0.5 ([ff87526](https://github.com/genu/nuxt-concierge/commit/ff87526)) 387 | - **release:** V1.0.6 ([f297224](https://github.com/genu/nuxt-concierge/commit/f297224)) 388 | - **release:** V1.0.7 ([f60bccc](https://github.com/genu/nuxt-concierge/commit/f60bccc)) 389 | - **release:** V1.0.8 ([1fe52fb](https://github.com/genu/nuxt-concierge/commit/1fe52fb)) 390 | - **release:** V1.0.9 ([f5e7774](https://github.com/genu/nuxt-concierge/commit/f5e7774)) 391 | - **release:** V1.0.10 ([eef58a3](https://github.com/genu/nuxt-concierge/commit/eef58a3)) 392 | - **release:** V1.0.11 ([a10724c](https://github.com/genu/nuxt-concierge/commit/a10724c)) 393 | - **release:** V1.0.12 ([117fdd2](https://github.com/genu/nuxt-concierge/commit/117fdd2)) 394 | - **release:** V1.0.13 ([28e2c2a](https://github.com/genu/nuxt-concierge/commit/28e2c2a)) 395 | - **release:** V1.0.14 ([9221c79](https://github.com/genu/nuxt-concierge/commit/9221c79)) 396 | - **release:** V1.0.15 ([e631128](https://github.com/genu/nuxt-concierge/commit/e631128)) 397 | - **release:** V1.0.16 ([8ab6b5e](https://github.com/genu/nuxt-concierge/commit/8ab6b5e)) 398 | - **release:** V1.0.17 ([eb6a4ba](https://github.com/genu/nuxt-concierge/commit/eb6a4ba)) 399 | - **release:** V1.0.18 ([31eae67](https://github.com/genu/nuxt-concierge/commit/31eae67)) 400 | - **release:** V1.0.19 ([94f2043](https://github.com/genu/nuxt-concierge/commit/94f2043)) 401 | - **release:** V1.0.20 ([0098965](https://github.com/genu/nuxt-concierge/commit/0098965)) 402 | - **release:** V1.0.21 ([4e63681](https://github.com/genu/nuxt-concierge/commit/4e63681)) 403 | - **release:** V1.0.22 ([b3ea6a4](https://github.com/genu/nuxt-concierge/commit/b3ea6a4)) 404 | - **release:** V1.0.23 ([26bb686](https://github.com/genu/nuxt-concierge/commit/26bb686)) 405 | - **release:** V1.0.24 ([ca20967](https://github.com/genu/nuxt-concierge/commit/ca20967)) 406 | 407 | ### ❤️ Contributors 408 | 409 | - Eugen Istoc 410 | 411 | ## v1.0.24 412 | 413 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.23...v1.0.24) 414 | 415 | ### 🏡 Chore 416 | 417 | - **release:** V1.0.23 ([26bb686](https://github.com/genu/nuxt-concierge/commit/26bb686)) 418 | 419 | ### ❤️ Contributors 420 | 421 | - Eugen Istoc 422 | 423 | ## v1.0.23 424 | 425 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.22...v1.0.23) 426 | 427 | ### 🏡 Chore 428 | 429 | - **release:** V1.0.22 ([b3ea6a4](https://github.com/genu/nuxt-concierge/commit/b3ea6a4)) 430 | 431 | ### ❤️ Contributors 432 | 433 | - Eugen Istoc 434 | 435 | ## v1.0.22 436 | 437 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.21...v1.0.22) 438 | 439 | ### 🏡 Chore 440 | 441 | - **release:** V1.0.21 ([4e63681](https://github.com/genu/nuxt-concierge/commit/4e63681)) 442 | 443 | ### ❤️ Contributors 444 | 445 | - Eugen Istoc 446 | 447 | ## v1.0.21 448 | 449 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.20...v1.0.21) 450 | 451 | ### 🏡 Chore 452 | 453 | - **release:** V1.0.20 ([0098965](https://github.com/genu/nuxt-concierge/commit/0098965)) 454 | 455 | ### ❤️ Contributors 456 | 457 | - Eugen Istoc 458 | 459 | ## v1.0.20 460 | 461 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.19...v1.0.20) 462 | 463 | ## v1.0.19 464 | 465 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.18...v1.0.19) 466 | 467 | ### 🏡 Chore 468 | 469 | - **release:** V1.0.18 ([31eae67](https://github.com/genu/nuxt-concierge/commit/31eae67)) 470 | 471 | ### ❤️ Contributors 472 | 473 | - Eugen Istoc 474 | 475 | ## v1.0.18 476 | 477 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.17...v1.0.18) 478 | 479 | ## v1.0.17 480 | 481 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.16...v1.0.17) 482 | 483 | ### 🏡 Chore 484 | 485 | - **release:** V1.0.16 ([8ab6b5e](https://github.com/genu/nuxt-concierge/commit/8ab6b5e)) 486 | 487 | ### ❤️ Contributors 488 | 489 | - Eugen Istoc 490 | 491 | ## v1.0.16 492 | 493 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.15...v1.0.16) 494 | 495 | ### 🏡 Chore 496 | 497 | - **release:** V1.0.15 ([e631128](https://github.com/genu/nuxt-concierge/commit/e631128)) 498 | 499 | ### ❤️ Contributors 500 | 501 | - Eugen Istoc 502 | 503 | ## v1.0.15 504 | 505 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.14...v1.0.15) 506 | 507 | ### 🏡 Chore 508 | 509 | - **release:** V1.0.14 ([9221c79](https://github.com/genu/nuxt-concierge/commit/9221c79)) 510 | 511 | ### ❤️ Contributors 512 | 513 | - Eugen Istoc 514 | 515 | ## v1.0.14 516 | 517 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.13...v1.0.14) 518 | 519 | ### 🏡 Chore 520 | 521 | - **release:** V1.0.13 ([28e2c2a](https://github.com/genu/nuxt-concierge/commit/28e2c2a)) 522 | 523 | ### ❤️ Contributors 524 | 525 | - Eugen Istoc 526 | 527 | ## v1.0.13 528 | 529 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.12...v1.0.13) 530 | 531 | ### 🏡 Chore 532 | 533 | - **release:** V1.0.12 ([117fdd2](https://github.com/genu/nuxt-concierge/commit/117fdd2)) 534 | 535 | ### ❤️ Contributors 536 | 537 | - Eugen Istoc 538 | 539 | ## v1.0.12 540 | 541 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.11...v1.0.12) 542 | 543 | ### 🏡 Chore 544 | 545 | - **release:** V1.0.11 ([a10724c](https://github.com/genu/nuxt-concierge/commit/a10724c)) 546 | 547 | ### ❤️ Contributors 548 | 549 | - Eugen Istoc 550 | 551 | ## v1.0.11 552 | 553 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.10...v1.0.11) 554 | 555 | ### 🏡 Chore 556 | 557 | - **release:** V1.0.10 ([eef58a3](https://github.com/genu/nuxt-concierge/commit/eef58a3)) 558 | 559 | ### ❤️ Contributors 560 | 561 | - Eugen Istoc 562 | 563 | ## v1.0.10 564 | 565 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.9...v1.0.10) 566 | 567 | ### 🏡 Chore 568 | 569 | - **release:** V1.0.9 ([f5e7774](https://github.com/genu/nuxt-concierge/commit/f5e7774)) 570 | 571 | ### ❤️ Contributors 572 | 573 | - Eugen Istoc 574 | 575 | ## v1.0.9 576 | 577 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.8...v1.0.9) 578 | 579 | ### 🏡 Chore 580 | 581 | - **release:** V1.0.8 ([1fe52fb](https://github.com/genu/nuxt-concierge/commit/1fe52fb)) 582 | 583 | ### ❤️ Contributors 584 | 585 | - Eugen Istoc 586 | 587 | ## v1.0.8 588 | 589 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.7...v1.0.8) 590 | 591 | ### 🏡 Chore 592 | 593 | - **release:** V1.0.7 ([f60bccc](https://github.com/genu/nuxt-concierge/commit/f60bccc)) 594 | 595 | ### ❤️ Contributors 596 | 597 | - Eugen Istoc 598 | 599 | ## v1.0.7 600 | 601 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.6...v1.0.7) 602 | 603 | ## v1.0.6 604 | 605 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.5...v1.0.6) 606 | 607 | ### 🏡 Chore 608 | 609 | - **release:** V1.0.5 ([ff87526](https://github.com/genu/nuxt-concierge/commit/ff87526)) 610 | 611 | ### ❤️ Contributors 612 | 613 | - Eugen Istoc 614 | 615 | ## v1.0.5 616 | 617 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.3...v1.0.5) 618 | 619 | ### 🏡 Chore 620 | 621 | - **release:** V1.0.3 ([b11f9f7](https://github.com/genu/nuxt-concierge/commit/b11f9f7)) 622 | - **release:** V1.0.4 ([84c5abf](https://github.com/genu/nuxt-concierge/commit/84c5abf)) 623 | 624 | ### ❤️ Contributors 625 | 626 | - Eugen Istoc 627 | 628 | ## v1.0.4 629 | 630 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.3...v1.0.4) 631 | 632 | ## v1.0.3 633 | 634 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.2...v1.0.3) 635 | 636 | ### 🏡 Chore 637 | 638 | - **release:** V1.0.2 ([7ea93ef](https://github.com/genu/nuxt-concierge/commit/7ea93ef)) 639 | 640 | ### ❤️ Contributors 641 | 642 | - Eugen Istoc 643 | 644 | ## v1.0.2 645 | 646 | [compare changes](https://github.com/genu/nuxt-concierge/compare/v1.0.1...v1.0.2) 647 | 648 | ### 🏡 Chore 649 | 650 | - **release:** V1.0.1 ([c3c7c89](https://github.com/genu/nuxt-concierge/commit/c3c7c89)) 651 | 652 | ### ❤️ Contributors 653 | 654 | - Eugen Istoc 655 | 656 | ## v1.0.1 657 | 658 | --------------------------------------------------------------------------------