├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .nuxtrc ├── README.md ├── package.json ├── playground ├── app.vue ├── nuxt.config.ts ├── package.json └── server │ ├── app │ └── services │ │ └── say.ts │ └── plugins │ ├── rocketScheduler.server.ts │ └── smile.server.ts ├── src ├── module.ts └── runtime │ └── server │ └── services │ ├── index.ts │ └── userScheduler.ts ├── test ├── basic.test.ts └── fixtures │ └── basic │ ├── app.vue │ ├── nuxt.config.ts │ └── package.json ├── tsconfig.json └── yarn.lock /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["@nuxt/eslint-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.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 | .vercel_build_output 23 | .build-* 24 | .env 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 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=true 2 | typescript.includeWorkspace=true 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![NuxtSchedulerBlack](https://user-images.githubusercontent.com/45824492/221433099-051fe13c-089d-4578-9021-a338a2aac80d.png) 3 | 4 | 12 | 13 | # Nuxt Scheduler 14 | 15 | [![npm version][npm-version-src]][npm-version-href] 16 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 17 | [![License][license-src]][license-href] 18 | [![Nuxt][nuxt-src]][nuxt-href] 19 | 20 | Schedule Jobs within Nuxt 3. 21 | 22 | > This is a Server-Side task scheduler for Nuxt which depends on node-cron. 23 | 24 | - [✨  Release Notes](/CHANGELOG.md) 25 | 26 | 27 | ## Features 28 | 29 | 30 | - 👩🏼‍🌾 human readable 31 | - 😌 easy to use 32 | 33 | ## Quick Setup 34 | 35 | 1. Add `nuxt-scheduler` dependency to your project 36 | 37 | ```bash 38 | # Using pnpm 39 | pnpm add -D nuxt-scheduler 40 | 41 | # Using yarn 42 | yarn add --dev nuxt-scheduler 43 | 44 | # Using npm 45 | npm install --save-dev nuxt-scheduler 46 | ``` 47 | 48 | 2. Add `nuxt-scheduler` to the `modules` section of `nuxt.config.ts` 49 | 50 | ```js 51 | export default defineNuxtConfig({ 52 | modules: [ 53 | 'nuxt-scheduler' 54 | ] 55 | }) 56 | ``` 57 | 58 | That's it! You can now use Nuxt Scheduler in your Nuxt app ✨ 59 | 60 | ## Example Usage 61 | Create as many scheduler you like as plugins. 62 | > ~/server/plugins/smileScheduler.ts 63 | 64 | ```js 65 | import { useScheduler } from "#scheduler" 66 | import say from "~~/server/app/services/say"; 67 | 68 | export default defineNitroPlugin(() => { 69 | startScheduler() 70 | }) 71 | 72 | function startScheduler() { 73 | const scheduler = useScheduler(); 74 | 75 | scheduler.run(() => { 76 | say("cool beans! I run once a second! 😀"); 77 | }).everySecond(); 78 | 79 | // create as many tasks as you want here 80 | } 81 | ``` 82 | 83 | Each scheduler can have multiple tasks 84 | > ~/server/plugins/rocketScheduler.ts 85 | 86 | ```js 87 | import { useScheduler } from "#scheduler" 88 | import say from "~~/server/app/services/say"; 89 | 90 | 91 | export default defineNitroPlugin(() => { 92 | startScheduler() 93 | }) 94 | 95 | function startScheduler() { 96 | const scheduler = useScheduler(); 97 | 98 | scheduler.run(() => { 99 | say("I run every 3 seconds, 🚀🚀🚀"); 100 | }).everySeconds(3); 101 | 102 | scheduler.run(() => { 103 | say("I run every 5 seconds, 🚀🚀🚀🚀🚀"); 104 | }).everySeconds(5); 105 | 106 | // create as many tasks as you want here 107 | } 108 | ``` 109 | 110 | > use Human Readable intervals 111 | ``` 112 | everySecond 113 | everySeconds 114 | everyMinute 115 | everyMinutes 116 | everyTwoMinutes 117 | everyThreeMinutes 118 | everyFourMinutes 119 | everyFiveMinutes 120 | everyTenMinutes 121 | everyFifteenMinutes 122 | everyThirtyMinutes 123 | hourly 124 | hourlyAt 125 | everyOddHour 126 | everyHours 127 | everyTwoHours 128 | everyThreeHours 129 | everyFourHours 130 | everySixHours 131 | daily 132 | dailyAt 133 | everyDays 134 | weekly 135 | quarterly 136 | yearly 137 | ``` 138 | 139 | > or set using cron method 140 | > 141 | `cron()` method accepts **'optional'** string parameter called `timezone` where you have to pass a valid string values **eg. Asia/Shanghai, Asia/Kolkata, America/Sao_Paulo** refer [IANA time zone database ](https://www.iana.org/time-zones) for same. 142 | ```js 143 | import { useScheduler } from "#scheduler" 144 | import say from "~~/server/app/services/say"; 145 | 146 | export default defineNitroPlugin(() => { 147 | startScheduler() 148 | }) 149 | 150 | function startScheduler() { 151 | const scheduler = useScheduler(); 152 | 153 | scheduler.run(() => { 154 | say("I run once a second! 🚀"); 155 | }).cron('* * * * *'); 156 | 157 | scheduler.run(() => { 158 | say("Running a job at 01:00 PM at America/Sao_Paulo timezone 🌍"); 159 | }).cron('0 13 * * *', 'America/Sao_Paulo'); 160 | 161 | // create as many tasks as you want here 162 | } 163 | ``` 164 | 165 | ## building Nuxt App with nuxt-scheduler 166 | > to prevent the scheduler from running during the build, you can do the following 167 | 168 | update the build command to add a new environment variable 169 | ```json 170 | "scripts": { 171 | "build": "APP_ENV=build nuxt build", 172 | } 173 | ``` 174 | 175 | then in your scheduler plugin, you can check for the environment variable 176 | ```js 177 | import { useScheduler } from "#scheduler" 178 | 179 | export default defineNitroPlugin(() => { 180 | //early return if build context 181 | if (process.env.APP_ENV === 'build') { 182 | console.log('[server/plugins/scheduler.ts] Skipping scheduler, in build context'); 183 | return; 184 | } 185 | startScheduler() 186 | }) 187 | 188 | function startScheduler() { 189 | 190 | const scheduler = useScheduler(); 191 | 192 | scheduler.run(() => { 193 | console.log("cool beans! I run once a second! 😀"); 194 | }).everySecond(); 195 | 196 | // create as many tasks as you want here 197 | } 198 | ``` 199 | 200 | ## Development 201 | 202 | ```bash 203 | # Install dependencies 204 | npm install 205 | 206 | # Generate type stubs 207 | npm run dev:prepare 208 | 209 | # Develop with the playground 210 | npm run dev 211 | 212 | # Build the playground 213 | npm run dev:build 214 | 215 | # Run ESLint 216 | npm run lint 217 | 218 | # Run Vitest 219 | npm run test 220 | npm run test:watch 221 | 222 | # Release new version 223 | npm run release 224 | ``` 225 | 226 | Special thanks to [Atinux](https://github.com/Atinux) for making suggestions that have made this module better 🚀 227 | 228 | 229 | [npm-version-src]: https://img.shields.io/npm/v/nuxt-scheduler/latest.svg?style=flat&colorA=18181B&colorB=28CF8D 230 | [npm-version-href]: https://npmjs.com/package/nuxt-scheduler 231 | 232 | [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-scheduler.svg?style=flat&colorA=18181B&colorB=28CF8D 233 | [npm-downloads-href]: https://npmjs.com/package/nuxt-scheduler 234 | 235 | [license-src]: https://img.shields.io/npm/l/nuxt-scheduler.svg?style=flat&colorA=18181B&colorB=28CF8D 236 | [license-href]: https://npmjs.com/package/nuxt-scheduler 237 | 238 | [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js 239 | [nuxt-href]: https://nuxt.com 240 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-scheduler", 3 | "version": "0.1.9", 4 | "description": "Schedule Jobs within Nuxt 3", 5 | "license": "MIT", 6 | "type": "module", 7 | "exports": { 8 | ".": { 9 | "types": "./dist/types.d.ts", 10 | "import": "./dist/module.mjs", 11 | "require": "./dist/module.cjs" 12 | } 13 | }, 14 | "main": "./dist/module.cjs", 15 | "types": "./dist/types.d.ts", 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "prepack": "nuxt-module-build", 21 | "dev": "nuxi dev playground", 22 | "dev:build": "nuxi build playground", 23 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", 24 | "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags", 25 | "lint": "eslint .", 26 | "test": "vitest run", 27 | "test:watch": "vitest watch" 28 | }, 29 | "dependencies": { 30 | "@nuxt/kit": "^3.2.2", 31 | "node-cron": "^3.0.2", 32 | "defu": "^6.1.0" 33 | }, 34 | "devDependencies": { 35 | "@nuxt/eslint-config": "^0.1.1", 36 | "@nuxt/module-builder": "^0.2.1", 37 | "@nuxt/schema": "^3.2.2", 38 | "@nuxt/test-utils": "^3.2.2", 39 | "changelogen": "^0.4.1", 40 | "eslint": "^8.34.0", 41 | "nuxt": "^3.2.2", 42 | "vitest": "^0.28.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: ['../src/module'], 3 | myModule: {} 4 | }) 5 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nuxt-scheduler-playground" 4 | } 5 | -------------------------------------------------------------------------------- /playground/server/app/services/say.ts: -------------------------------------------------------------------------------- 1 | export default function say(message: string) { 2 | console.log(message); 3 | } 4 | -------------------------------------------------------------------------------- /playground/server/plugins/rocketScheduler.server.ts: -------------------------------------------------------------------------------- 1 | import { useScheduler } from "#scheduler" 2 | import say from "~~/server/app/services/say"; 3 | 4 | 5 | export default defineNitroPlugin(() => { 6 | startScheduler() 7 | }) 8 | 9 | function startScheduler() { 10 | const scheduler = useScheduler(); 11 | 12 | scheduler.run(() => { 13 | say("I run every 3 seconds, 🚀🚀🚀"); 14 | }).everySeconds(3); 15 | 16 | scheduler.run(() => { 17 | say("I run every 5 seconds, 🚀🚀🚀🚀🚀"); 18 | }).everySeconds(5); 19 | 20 | // create as many tasks as you want here 21 | } 22 | -------------------------------------------------------------------------------- /playground/server/plugins/smile.server.ts: -------------------------------------------------------------------------------- 1 | import { useScheduler } from "#scheduler" 2 | import say from "~~/server/app/services/say"; 3 | 4 | export default defineNitroPlugin(() => { 5 | startScheduler() 6 | }) 7 | 8 | function startScheduler() { 9 | const scheduler = useScheduler(); 10 | 11 | scheduler.run(() => { 12 | say("cool beans! I run once a second! 😀"); 13 | }).everySecond(); 14 | 15 | scheduler.run(() => { 16 | say("Running a job at 01:00 PM at America/Sao_Paulo timezone 😀"); 17 | }).cron('0 13 * * *', 'America/Sao_Paulo'); 18 | 19 | // create as many tasks as you want here 20 | } 21 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule, createResolver, addTemplate, useLogger } from '@nuxt/kit' 2 | import defu from 'defu' 3 | 4 | export interface ModuleOptions { } 5 | 6 | export default defineNuxtModule({ 7 | meta: { 8 | name: 'nuxt-scheduler', 9 | configKey: 'nuxtScheduler', 10 | compatibility: { 11 | nuxt: '^3.0.0' 12 | } 13 | }, 14 | defaults: { 15 | addPlugin: false 16 | }, 17 | setup(options, nuxt) { 18 | const { resolve } = createResolver(import.meta.url) 19 | 20 | nuxt.hook('nitro:config', (nitroConfig) => { 21 | nitroConfig.alias = nitroConfig.alias || {} 22 | nitroConfig.externals = defu(typeof nitroConfig.externals === 'object' ? nitroConfig.externals : {}, { 23 | inline: [resolve('./runtime')] 24 | }) 25 | nitroConfig.alias['#scheduler'] = resolve('./runtime/server/services') 26 | }) 27 | 28 | addTemplate({ 29 | filename: 'types/scheduler.d.ts', 30 | getContents: () => [ 31 | 'declare module \'#scheduler\' {', 32 | ` const useScheduler: typeof import('${resolve('./runtime/server/services')}').useScheduler`, 33 | '}' 34 | ].join('\n') 35 | }) 36 | 37 | nuxt.hook('prepare:types', (options) => { 38 | options.references.push({ path: resolve(nuxt.options.buildDir, 'types/scheduler.d.ts') }) 39 | }) 40 | } 41 | }) 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/runtime/server/services/index.ts: -------------------------------------------------------------------------------- 1 | export {useScheduler} from './userScheduler' 2 | -------------------------------------------------------------------------------- /src/runtime/server/services/userScheduler.ts: -------------------------------------------------------------------------------- 1 | import cron from 'node-cron'; 2 | 3 | export interface Scheduler { 4 | everySecond: () => void; 5 | everySeconds: (seconds: number) => void; 6 | everyMinute: () => void; 7 | everyMinutes: (minutes: number) => void; 8 | everyTwoMinutes: () => void; 9 | everyThreeMinutes: () => void; 10 | everyFourMinutes: () => void; 11 | everyFiveMinutes: () => void; 12 | everyTenMinutes: () => void; 13 | everyFifteenMinutes: () => void; 14 | everyThirtyMinutes: () => void; 15 | hourly: () => void; 16 | hourlyAt: (minute: number) => void; 17 | everyOddHour: () => void; 18 | everyHours: (hours: number) => void; 19 | everyTwoHours: () => void; 20 | everyThreeHours: () => void; 21 | everyFourHours: () => void; 22 | everySixHours: () => void; 23 | daily: () => void; 24 | dailyAt: (hour: number, minute: number) => void; 25 | everyDays: (days: number) => void; 26 | weekly: () => void; 27 | quarterly: () => void; 28 | yearly: () => void; 29 | cron: (interval: string, timezone?: string) => void; 30 | } 31 | 32 | function run(callback: Function): Scheduler { 33 | return { 34 | everySecond: () => { 35 | cron.schedule('* * * * * *', callback); 36 | }, 37 | everySeconds: (seconds: number = 1) => { 38 | cron.schedule(`*/${seconds} * * * * *`, callback); 39 | }, 40 | everyMinute: () => { 41 | cron.schedule('* * * * *', callback); 42 | }, 43 | everyTwoMinutes: () => { 44 | cron.schedule('*/2 * * * *', callback); 45 | }, 46 | everyThreeMinutes: () => { 47 | cron.schedule('*/3 * * * *', callback); 48 | }, 49 | everyFourMinutes: () => { 50 | cron.schedule('*/4 * * * *', callback); 51 | }, 52 | everyFiveMinutes: () => { 53 | cron.schedule('*/5 * * * *', callback); 54 | }, 55 | everyTenMinutes: () => { 56 | cron.schedule('*/10 * * * *', callback); 57 | }, 58 | everyFifteenMinutes: () => { 59 | cron.schedule('*/15 * * * *', callback); 60 | }, 61 | everyThirtyMinutes: () => { 62 | cron.schedule('*/30 * * * *', callback); 63 | }, 64 | everyMinutes: (minutes: number) => { 65 | cron.schedule(`*/${minutes} * * * *`, callback); 66 | }, 67 | hourly: () => { 68 | cron.schedule('0 * * * *', callback); 69 | }, 70 | everyHours: (hours: number) => { 71 | cron.schedule(`0 */${hours} * * *`, callback); 72 | }, 73 | hourlyAt: (minute: number) => { 74 | cron.schedule(`${minute} * * * *`, callback); 75 | }, 76 | everyOddHour: () => { 77 | cron.schedule('0 */2 * * *', callback); 78 | }, 79 | everyTwoHours: () => { 80 | cron.schedule('0 */2 * * *', callback); 81 | }, 82 | everyThreeHours: () => { 83 | cron.schedule('0 */3 * * *', callback); 84 | }, 85 | everyFourHours: () => { 86 | cron.schedule('0 */4 * * *', callback); 87 | }, 88 | everySixHours: () => { 89 | cron.schedule('0 */6 * * *', callback); 90 | }, 91 | dailyAt: (hour: number, minute: number) => { 92 | cron.schedule(`${minute} ${hour} * * *`, callback); 93 | }, 94 | daily: () => { 95 | cron.schedule('0 0 * * *', callback); 96 | }, 97 | everyDays: (days: number) => { 98 | cron.schedule(`0 0 */${days} * *`, callback); 99 | }, 100 | weekly: () => { 101 | cron.schedule('0 0 * * 0', callback); 102 | }, 103 | quarterly: () => { 104 | cron.schedule('0 0 1 */3 *', callback); 105 | }, 106 | yearly: () => { 107 | cron.schedule('0 0 1 1 *', callback); 108 | }, 109 | cron: (interval: string, timezone?: string) => { 110 | timezone ? cron.schedule(interval, callback, { scheduled: true, timezone: timezone }) : cron.schedule(interval, callback); 111 | } 112 | }; 113 | } 114 | 115 | export function useScheduler() { 116 | return { 117 | run 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | import { fileURLToPath } from 'node:url' 3 | import { setup, $fetch } from '@nuxt/test-utils' 4 | 5 | describe('ssr', async () => { 6 | await setup({ 7 | rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)), 8 | }) 9 | 10 | it('renders the index page', async () => { 11 | // Get response to a server-rendered page with `$fetch`. 12 | const html = await $fetch('/') 13 | expect(html).toContain('
basic
') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/fixtures/basic/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /test/fixtures/basic/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import MyModule from '../../../src/module' 2 | 3 | export default defineNuxtConfig({ 4 | modules: [ 5 | MyModule 6 | ] 7 | }) 8 | -------------------------------------------------------------------------------- /test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "basic", 4 | "type": "module" 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | --------------------------------------------------------------------------------