diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..fbb3d933 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,12 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "TypeScript SDK", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", + "postCreateCommand": "cd examples && npm run setup", + "customizations": { + "codespaces":{ + "openFiles": ["examples/src/index.ts", "README.md"] + } + } +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..36d76d7d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,50 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es2021": true + }, + "extends": [ + "airbnb-base", + "airbnb-typescript/base", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "project": "./tsconfig.eslint.json" + }, + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "rules": { + "no-console": "off", + "max-len": [ + "error", + { + "code": 150, + "ignoreComments": true, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + "prettier/prettier": "error", + "@typescript-eslint/dot-notation": "off", + "import/prefer-default-export": "off" + }, + "settings": { + "import/resolver": { + "node": { + "extensions": [ + ".js", + ".jsx", + ".ts", + ".tsx" + ] + } + } + } +} diff --git a/.github/PROTECTED_BRANCHES b/.github/PROTECTED_BRANCHES new file mode 100644 index 00000000..ba2906d0 --- /dev/null +++ b/.github/PROTECTED_BRANCHES @@ -0,0 +1 @@ +main diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml new file mode 100644 index 00000000..10aff5ca --- /dev/null +++ b/.github/workflows/build-checks.yml @@ -0,0 +1,35 @@ +name: Release Checks + +on: + push: + branches: + - main + +jobs: + github-publish: + runs-on: ubuntu-latest + + steps: + + - name: Checkout Repository & Submo + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install dependencies + run: npm install + + # TODO: Finish fixing eslint issues + # - name: Run ESLint check + # run: npm run lint:ci + + - name: Run Test & Coverage check + run: npm run test + diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 00000000..7aaa3651 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,28 @@ +name: Pull Request Checks + +on: [pull_request] + +jobs: + linting-and-testing: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install dependencies + run: npm install + + # TODO: Finish fixing eslint issues + # - name: Run ESLint check + # run: npm run lint:ci + + - name: Run Test & Coverage check + run: npm run test diff --git a/.github/workflows/release-checks.yml b/.github/workflows/release-checks.yml new file mode 100644 index 00000000..2467c283 --- /dev/null +++ b/.github/workflows/release-checks.yml @@ -0,0 +1,32 @@ +name: Release Checks + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + - run: npm ci + - run: npm run test + + npm-publish: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + diff --git a/.gitignore b/.gitignore index 0b6abc16..4d73d47b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,132 @@ -node_modules -build +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt dist -api-specs -.env \ No newline at end of file + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +.parcel-cache diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..b09cba12 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,26 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "overrides": [ + { + "files": ".editorconfig", + "options": { + "parser": "yaml" + } + }, + { + "files": "LICENSE", + "options": { + "parser": "markdown" + } + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cb511c80 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..2adc2976 --- /dev/null +++ b/README.md @@ -0,0 +1,2035 @@ +# PlexSDK Typescript SDK 0.0.1 +The Typescript SDK for PlexSDK. +- API version: 0.0.1 +- SDK version: 0.0.1 +## Table of Contents +- [About the API](#requirements) +- [Installation](#installation) +- [Authentication](#authentication) + - [API Key](#api-key) +- [API Endpoint Services](#api-endpoint-services) +- [API Models](#api-models) +- [Sample Usage](#sample-usage) +- [PlexSDK Services](#plexsdk-services) +- [License](#license) +## About the API +An Open API Spec for interacting with Plex.tv and Plex Servers +## Installation +```sh +npm install plexjs +``` +## Authentication +To see whether an endpoint needs a specific type of authentication check the endpoint's documentation. +### API Key +The PlexSDK API uses API keys as a form of authentication. An API key is a unique identifier used to authenticate a user, developer, or a program that is calling the API. You can set the API key when initializing the SDK through the constructor: + +```Typescript +sdk = new PlexSDK('YOUR_API_KEY', 'YOUR_API_KEY_HEADER'); +``` + +If you omit `YOUR_API_KEY_HEADER`, the SDK default API key header will be `X-Plex-Token`. + +You can also set it for each service individually: +```Typescript +const sdk = new PlexSDK() +sdk.server.setApiKey('YOUR_API_KEY', 'YOUR_API_KEY_HEADER'); +``` +## Sample Usage +Here is a simple program demonstrating usage of this SDK. It can also be found in the `examples/src/index.ts` file in this directory. + +When running the sample make sure to use `npm install` to install all the dependencies. + +```Typescript +import { PlexSDK } from '@lukehagar/plexjs'; + + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server + .getServerCapabilities(); + console.log(result.data); +})(); + + +``` +# PlexSDK Services +A list of all services and services methods. +- Services + + - [Server](#server) + + - [Media](#media) + + - [Activities](#activities) + + - [Butler](#butler) + + - [Hubs](#hubs) + + - [Search](#search) + + - [Library](#library) + + - [Log](#log) + + - [Playlists](#playlists) + + - [Security](#security) + + - [Sessions](#sessions) + + - [Updater](#updater) + + - [Video](#video) +- [All Methods](#all-methods) + + +## Server + +| Method | Description| +| :-------- | :----------| +| [getServerCapabilities](#getservercapabilities) | Server Capabilities | +| [getServerPreferences](#getserverpreferences) | Get Server Preferences | +| [getAvailableClients](#getavailableclients) | Get Available Clients | +| [getDevices](#getdevices) | Get Devices | +| [getServerIdentity](#getserveridentity) | Get Server Identity | +| [getMyPlexAccount](#getmyplexaccount) | Get MyPlex Account | +| [getResizedPhoto](#getresizedphoto) | Get a Resized Photo | +| [getServerList](#getserverlist) | Get Server List | + + +## Media + +| Method | Description| +| :-------- | :----------| +| [markPlayed](#markplayed) | Mark Media Played | +| [markUnplayed](#markunplayed) | Mark Media Unplayed | +| [updatePlayProgress](#updateplayprogress) | Update Media Play Progress | + + +## Activities + +| Method | Description| +| :-------- | :----------| +| [getServerActivities](#getserveractivities) | Get Server Activities | +| [cancelServerActivities](#cancelserveractivities) | Cancel Server Activities | + + +## Butler + +| Method | Description| +| :-------- | :----------| +| [startAllTasks](#startalltasks) | Start all Butler tasks | +| [getButlerTasks](#getbutlertasks) | Get Butler tasks | +| [stopAllTasks](#stopalltasks) | Stop all Butler tasks | +| [startTask](#starttask) | Start a single Butler task | +| [stopTask](#stoptask) | Stop a single Butler task | + + +## Hubs + +| Method | Description| +| :-------- | :----------| +| [getGlobalHubs](#getglobalhubs) | Get Global Hubs | +| [getLibraryHubs](#getlibraryhubs) | Get library specific hubs | + + +## Search + +| Method | Description| +| :-------- | :----------| +| [performSearch](#performsearch) | Perform a search | +| [performVoiceSearch](#performvoicesearch) | Perform a voice search | +| [getSearchResults](#getsearchresults) | Get Search Results | + + +## Library + +| Method | Description| +| :-------- | :----------| +| [getFileHash](#getfilehash) | Get Hash Value | +| [getRecentlyAdded](#getrecentlyadded) | Get Recently Added | +| [getLibraries](#getlibraries) | Get All Libraries | +| [getLibrary](#getlibrary) | Get Library Details | +| [deleteLibrary](#deletelibrary) | Delete Library Section | +| [getLibraryItems](#getlibraryitems) | Get Library Items | +| [refreshLibrary](#refreshlibrary) | Refresh Library | +| [getLatestLibraryItems](#getlatestlibraryitems) | Get Latest Library Items | +| [getCommonLibraryItems](#getcommonlibraryitems) | Get Common Library Items | +| [getMetadata](#getmetadata) | Get Items Metadata | +| [getMetadataChildren](#getmetadatachildren) | Get Items Children | +| [getOnDeck](#getondeck) | Get On Deck | + + +## Log + +| Method | Description| +| :-------- | :----------| +| [logMultiLine](#logmultiline) | Logging a multi-line message | +| [logLine](#logline) | Logging a single line message. | +| [enablePaperTrail](#enablepapertrail) | Enabling Papertrail | + + +## Playlists + +| Method | Description| +| :-------- | :----------| +| [createPlaylist](#createplaylist) | Create a Playlist | +| [getPlaylists](#getplaylists) | Get All Playlists | +| [getPlaylist](#getplaylist) | Retrieve Playlist | +| [deletePlaylist](#deleteplaylist) | Deletes a Playlist | +| [updatePlaylist](#updateplaylist) | Update a Playlist | +| [getPlaylistContents](#getplaylistcontents) | Retrieve Playlist Contents | +| [clearPlaylistContents](#clearplaylistcontents) | Delete Playlist Contents | +| [addPlaylistContents](#addplaylistcontents) | Adding to a Playlist | +| [uploadPlaylist](#uploadplaylist) | Upload Playlist | + + +## Security + +| Method | Description| +| :-------- | :----------| +| [getTransientToken](#gettransienttoken) | Get a Transient Token. | +| [getSourceConnectionInformation](#getsourceconnectioninformation) | Get Source Connection Information | + + +## Sessions + +| Method | Description| +| :-------- | :----------| +| [getSessions](#getsessions) | Get Active Sessions | +| [getSessionHistory](#getsessionhistory) | Get Session History | +| [getTranscodeSessions](#gettranscodesessions) | Get Transcode Sessions | +| [stopTranscodeSession](#stoptranscodesession) | Stop a Transcode Session | + + +## Updater + +| Method | Description| +| :-------- | :----------| +| [getUpdateStatus](#getupdatestatus) | Querying status of updates | +| [checkForUpdates](#checkforupdates) | Checking for updates | +| [applyUpdates](#applyupdates) | Apply Updates | + + +## Video + +| Method | Description| +| :-------- | :----------| +| [startUniversalTranscode](#startuniversaltranscode) | Start Universal Transcode | +| [getTimeline](#gettimeline) | Get the timeline for a media item | + + + + +## All Methods + + +### **getServerCapabilities** +Server Capabilities +- HTTP Method: GET +- Endpoint: / + + +**Return Type** + +GetServerCapabilitiesResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerCapabilities(); + console.log(result.data); +})(); + +``` + +### **getServerPreferences** +Get Server Preferences +- HTTP Method: GET +- Endpoint: /:/prefs + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerPreferences(); + console.log(result.data); +})(); + +``` + +### **getAvailableClients** +Get Available Clients +- HTTP Method: GET +- Endpoint: /clients + + +**Return Type** + +GetAvailableClientsResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getAvailableClients(); + console.log(result.data); +})(); + +``` + +### **getDevices** +Get Devices +- HTTP Method: GET +- Endpoint: /devices + + +**Return Type** + +GetDevicesResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getDevices(); + console.log(result.data); +})(); + +``` + +### **getServerIdentity** +Get Server Identity +- HTTP Method: GET +- Endpoint: /identity + + +**Return Type** + +GetServerIdentityResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerIdentity(); + console.log(result.data); +})(); + +``` + +### **getMyPlexAccount** +Get MyPlex Account +- HTTP Method: GET +- Endpoint: /myplex/account + + +**Return Type** + +GetMyPlexAccountResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getMyPlexAccount(); + console.log(result.data); +})(); + +``` + +### **getResizedPhoto** +Get a Resized Photo +- HTTP Method: GET +- Endpoint: /photo/:/transcode + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| width | number | The width for the resized photo | +| height | number | The height for the resized photo | +| opacity | number | The opacity for the resized photo | +| blur | number | The width for the resized photo | +| minSize | [MinSize](/src/models/README.md#minsize) | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. | +| upscale | [Upscale](/src/models/README.md#upscale) | allow images to be resized beyond native dimensions. | +| url | string | path to image within Plex | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getResizedPhoto( + 110, + 165, + 100, + 4000, + 1, + 1, + '/library/metadata/49564/thumb/1654258204', + ); + console.log(result.data); +})(); + +``` + +### **getServerList** +Get Server List +- HTTP Method: GET +- Endpoint: /servers + + +**Return Type** + +GetServerListResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerList(); + console.log(result.data); +})(); + +``` + + +### **markPlayed** +Mark Media Played +- HTTP Method: GET +- Endpoint: /:/scrobble + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| key | number | The media key to mark as played | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.media.markPlayed(59398); + console.log(result.data); +})(); + +``` + +### **markUnplayed** +Mark Media Unplayed +- HTTP Method: GET +- Endpoint: /:/unscrobble + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| key | number | The media key to mark as Unplayed | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.media.markUnplayed(59398); + console.log(result.data); +})(); + +``` + +### **updatePlayProgress** +Update Media Play Progress +- HTTP Method: POST +- Endpoint: /:/progress + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| key | string | the media key | +| time | number | The time, in milliseconds, used to set the media playback progress. | +| state | string | The playback state of the media item. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.media.updatePlayProgress('key', -67598765.07465324, 'state'); + console.log(result.data); +})(); + +``` + + +### **getServerActivities** +Get Server Activities +- HTTP Method: GET +- Endpoint: /activities + + +**Return Type** + +GetServerActivitiesResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.activities.getServerActivities(); + console.log(result.data); +})(); + +``` + +### **cancelServerActivities** +Cancel Server Activities +- HTTP Method: DELETE +- Endpoint: /activities/{activityUUID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| activityUuid | string | The UUID of the activity to cancel. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.activities.cancelServerActivities('activityUUID'); + console.log(result.data); +})(); + +``` + + +### **startAllTasks** +Start all Butler tasks +- HTTP Method: POST +- Endpoint: /butler + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.startAllTasks(); + console.log(result.data); +})(); + +``` + +### **getButlerTasks** +Get Butler tasks +- HTTP Method: GET +- Endpoint: /butler + + +**Return Type** + +GetButlerTasksResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.getButlerTasks(); + console.log(result.data); +})(); + +``` + +### **stopAllTasks** +Stop all Butler tasks +- HTTP Method: DELETE +- Endpoint: /butler + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.stopAllTasks(); + console.log(result.data); +})(); + +``` + +### **startTask** +Start a single Butler task +- HTTP Method: POST +- Endpoint: /butler/{taskName} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| taskName | [TaskName](/src/models/README.md#taskname) | the name of the task to be started. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.startTask('RefreshLibraries'); + console.log(result.data); +})(); + +``` + +### **stopTask** +Stop a single Butler task +- HTTP Method: DELETE +- Endpoint: /butler/{taskName} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| taskName | [TaskName](/src/models/README.md#taskname) | The name of the task to be started. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.stopTask('DeepMediaAnalysis'); + console.log(result.data); +})(); + +``` + + +### **getGlobalHubs** +Get Global Hubs +- HTTP Method: GET +- Endpoint: /hubs + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| count | number | The number of items to return with each hub. | +| onlyTransient | [OnlyTransient](/src/models/README.md#onlytransient) | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.hubs.getGlobalHubs({ count: 28368865.713739887, onlyTransient: 1 }); + console.log(result.data); +})(); + +``` + +### **getLibraryHubs** +Get library specific hubs +- HTTP Method: GET +- Endpoint: /hubs/sections/{sectionId} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| count | number | The number of items to return with each hub. | +| onlyTransient | [OnlyTransient](/src/models/README.md#onlytransient) | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.hubs.getLibraryHubs(18589771.015234828, { + count: -32594130.898548722, + onlyTransient: 1, + }); + console.log(result.data); +})(); + +``` + + +### **performSearch** +Perform a search +- HTTP Method: GET +- Endpoint: /hubs/search + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| query | string | The query term | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | number | The number of items to return per hub | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.search.performSearch('arnold', { + sectionId: 44691501.34992224, + limit: 5, + }); + console.log(result.data); +})(); + +``` + +### **performVoiceSearch** +Perform a voice search +- HTTP Method: GET +- Endpoint: /hubs/search/voice + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| query | string | The query term | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | number | The number of items to return per hub | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.search.performVoiceSearch('dead+poop', { + sectionId: 63927189.69130659, + limit: 5, + }); + console.log(result.data); +})(); + +``` + +### **getSearchResults** +Get Search Results +- HTTP Method: GET +- Endpoint: /search + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| query | string | The search query string to use | + + + +**Return Type** + +GetSearchResultsResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.search.getSearchResults('110'); + console.log(result.data); +})(); + +``` + + +### **getFileHash** +Get Hash Value +- HTTP Method: GET +- Endpoint: /library/hashes + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| url | string | This is the path to the local file, must be prefixed by `file://` | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| type | number | Item type | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getFileHash('file://C:Image.png&type=13', { + type: -40347992.189137824, + }); + console.log(result.data); +})(); + +``` + +### **getRecentlyAdded** +Get Recently Added +- HTTP Method: GET +- Endpoint: /library/recentlyAdded + + +**Return Type** + +GetRecentlyAddedResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getRecentlyAdded(); + console.log(result.data); +})(); + +``` + +### **getLibraries** +Get All Libraries +- HTTP Method: GET +- Endpoint: /library/sections + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLibraries(); + console.log(result.data); +})(); + +``` + +### **getLibrary** +Get Library Details +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| includeDetails | [IncludeDetails](/src/models/README.md#includedetails) | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
| + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLibrary(1000, { includeDetails: 42 }); + console.log(result.data); +})(); + +``` + +### **deleteLibrary** +Delete Library Section +- HTTP Method: DELETE +- Endpoint: /library/sections/{sectionId} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.deleteLibrary(1000); + console.log(result.data); +})(); + +``` + +### **getLibraryItems** +Get Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/all + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| type | number | item type | +| filter | string | the filter parameter | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLibraryItems(72139049.19684184, { + type: -38282492.854482666, + filter: 'filter', + }); + console.log(result.data); +})(); + +``` + +### **refreshLibrary** +Refresh Library +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/refresh + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to refresh | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.refreshLibrary(-48997883.13740698); + console.log(result.data); +})(); + +``` + +### **getLatestLibraryItems** +Get Latest Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/latest + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | +| type | number | item type | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| filter | string | the filter parameter | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLatestLibraryItems(58797973.85610509, -91925704.44457702, { + filter: 'filter', + }); + console.log(result.data); +})(); + +``` + +### **getCommonLibraryItems** +Get Common Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/common + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | +| type | number | item type | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| filter | string | the filter parameter | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getCommonLibraryItems(-1929766.2595285624, 10178756.232577473, { + filter: 'filter', + }); + console.log(result.data); +})(); + +``` + +### **getMetadata** +Get Items Metadata +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| ratingKey | number | the id of the library item to return the children of. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getMetadata(-93116760.96986176); + console.log(result.data); +})(); + +``` + +### **getMetadataChildren** +Get Items Children +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey}/children + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| ratingKey | number | the id of the library item to return the children of. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getMetadataChildren(74240120.76203653); + console.log(result.data); +})(); + +``` + +### **getOnDeck** +Get On Deck +- HTTP Method: GET +- Endpoint: /library/onDeck + + +**Return Type** + +GetOnDeckResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getOnDeck(); + console.log(result.data); +})(); + +``` + + +### **logMultiLine** +Logging a multi-line message +- HTTP Method: POST +- Endpoint: /log + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.log.logMultiLine(); + console.log(result.data); +})(); + +``` + +### **logLine** +Logging a single line message. +- HTTP Method: GET +- Endpoint: /log + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| level | [Level](/src/models/README.md#level) | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
| +| message | string | The text of the message to write to the log. | +| source | string | a string indicating the source of the message. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.log.logLine(4, 'message', 'source'); + console.log(result.data); +})(); + +``` + +### **enablePaperTrail** +Enabling Papertrail +- HTTP Method: GET +- Endpoint: /log/networked + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.log.enablePaperTrail(); + console.log(result.data); +})(); + +``` + + +### **createPlaylist** +Create a Playlist +- HTTP Method: POST +- Endpoint: /playlists + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| title | string | name of the playlist | +| type | [Type](/src/models/README.md#type) | type of playlist to create | +| smart | [Smart](/src/models/README.md#smart) | whether the playlist is smart or not | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| uri | string | the content URI for the playlist | +| playQueueId | number | the play queue to copy to a playlist | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.createPlaylist('title', 'video', 1, { + uri: 'uri', + playQueueID: 8848570.752491549, + }); + console.log(result.data); +})(); + +``` + +### **getPlaylists** +Get All Playlists +- HTTP Method: GET +- Endpoint: /playlists/all + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistType | [PlaylistType](/src/models/README.md#playlisttype) | limit to a type of playlist. | +| smart | [Smart](/src/models/README.md#smart) | type of playlists to return (default is all). | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.getPlaylists({ playlistType: 'photo', smart: 1 }); + console.log(result.data); +})(); + +``` + +### **getPlaylist** +Retrieve Playlist +- HTTP Method: GET +- Endpoint: /playlists/{playlistID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.getPlaylist(-26157175.250883877); + console.log(result.data); +})(); + +``` + +### **deletePlaylist** +Deletes a Playlist +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.deletePlaylist(98575271.53317443); + console.log(result.data); +})(); + +``` + +### **updatePlaylist** +Update a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.updatePlaylist(-63882285.47098422); + console.log(result.data); +})(); + +``` + +### **getPlaylistContents** +Retrieve Playlist Contents +- HTTP Method: GET +- Endpoint: /playlists/{playlistID}/items + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | +| type | number | the metadata type of the item to return | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.getPlaylistContents(39702400.82414672, 90162914.95216954); + console.log(result.data); +})(); + +``` + +### **clearPlaylistContents** +Delete Playlist Contents +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID}/items + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.clearPlaylistContents(12387577.811442614); + console.log(result.data); +})(); + +``` + +### **addPlaylistContents** +Adding to a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID}/items + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | +| uri | string | the content URI for the playlist | +| playQueueId | number | the play queue to add to a playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.addPlaylistContents(68171801.32332289, 'library://..', 123); + console.log(result.data); +})(); + +``` + +### **uploadPlaylist** +Upload Playlist +- HTTP Method: POST +- Endpoint: /playlists/upload + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| path | string | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
| +| force | [Force](/src/models/README.md#force) | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
| + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.uploadPlaylist('/home/barkley/playlist.m3u', 1); + console.log(result.data); +})(); + +``` + + +### **getTransientToken** +Get a Transient Token. +- HTTP Method: GET +- Endpoint: /security/token + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| type | [SecurityType](/src/models/README.md#securitytype) | `delegation` - This is the only supported `type` parameter. | +| scope | [Scope](/src/models/README.md#scope) | `all` - This is the only supported `scope` parameter. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.security.getTransientToken('delegation', 'all'); + console.log(result.data); +})(); + +``` + +### **getSourceConnectionInformation** +Get Source Connection Information +- HTTP Method: GET +- Endpoint: /security/resources + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| source | string | The source identifier with an included prefix. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.security.getSourceConnectionInformation( + 'provider://provider-identifier', + ); + console.log(result.data); +})(); + +``` + + +### **getSessions** +Get Active Sessions +- HTTP Method: GET +- Endpoint: /status/sessions + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.getSessions(); + console.log(result.data); +})(); + +``` + +### **getSessionHistory** +Get Session History +- HTTP Method: GET +- Endpoint: /status/sessions/history/all + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.getSessionHistory(); + console.log(result.data); +})(); + +``` + +### **getTranscodeSessions** +Get Transcode Sessions +- HTTP Method: GET +- Endpoint: /transcode/sessions + + +**Return Type** + +GetTranscodeSessionsResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.getTranscodeSessions(); + console.log(result.data); +})(); + +``` + +### **stopTranscodeSession** +Stop a Transcode Session +- HTTP Method: DELETE +- Endpoint: /transcode/sessions/{sessionKey} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sessionKey | string | the Key of the transcode session to stop | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.stopTranscodeSession('zz7llzqlx8w9vnrsbnwhbmep'); + console.log(result.data); +})(); + +``` + + +### **getUpdateStatus** +Querying status of updates +- HTTP Method: GET +- Endpoint: /updater/status + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.updater.getUpdateStatus(); + console.log(result.data); +})(); + +``` + +### **checkForUpdates** +Checking for updates +- HTTP Method: PUT +- Endpoint: /updater/check + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| download | [Download](/src/models/README.md#download) | Indicate that you want to start download any updates found. | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.updater.checkForUpdates({ download: 1 }); + console.log(result.data); +})(); + +``` + +### **applyUpdates** +Apply Updates +- HTTP Method: PUT +- Endpoint: /updater/apply + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| tonight | [Tonight](/src/models/README.md#tonight) | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install | +| skip | [Skip](/src/models/README.md#skip) | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.updater.applyUpdates({ tonight: 1, skip: 1 }); + console.log(result.data); +})(); + +``` + + +### **startUniversalTranscode** +Start Universal Transcode +- HTTP Method: GET +- Endpoint: /video/:/transcode/universal/start.mpd + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| hasMde | number | Whether the media item has MDE | +| path | string | The path to the media item to transcode | +| mediaIndex | number | The index of the media item to transcode | +| partIndex | number | The index of the part to transcode | +| protocol | string | The protocol to use for the transcode session | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| fastSeek | number | Whether to use fast seek or not | +| directPlay | number | Whether to use direct play or not | +| directStream | number | Whether to use direct stream or not | +| subtitleSize | number | The size of the subtitles | +| subtites | string | The subtitles | +| audioBoost | number | The audio boost | +| location | string | The location of the transcode session | +| mediaBufferSize | number | The size of the media buffer | +| session | string | The session ID | +| addDebugOverlay | number | Whether to add a debug overlay or not | +| autoAdjustQuality | number | Whether to auto adjust quality or not | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.video.startUniversalTranscode( + -72522752.76989551, + 'path', + 82041936.41398731, + -84753516.90588528, + 'protocol', + { + fastSeek: -30142578.11776738, + directPlay: 9919137.354697362, + directStream: -19025857.514729157, + subtitleSize: -84468293.84527272, + subtites: 'subtites', + audioBoost: -68940176.38612957, + location: 'location', + mediaBufferSize: 30606631.50083433, + session: 'session', + addDebugOverlay: -70696815.34237362, + autoAdjustQuality: -83810577.71928595, + }, + ); + console.log(result.data); +})(); + +``` + +### **getTimeline** +Get the timeline for a media item +- HTTP Method: GET +- Endpoint: /:/timeline + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| ratingKey | number | The rating key of the media item | +| key | string | The key of the media item to get the timeline for | +| state | [State](/src/models/README.md#state) | The state of the media item | +| hasMde | number | Whether the media item has MDE | +| time | number | The time of the media item | +| duration | number | The duration of the media item | +| context | string | The context of the media item | +| playQueueItemId | number | The play queue item ID of the media item | +| playBackTime | number | The playback time of the media item | +| row | number | The row of the media item | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.video.getTimeline( + 64550873.97298089, + 'key', + 'playing', + -30122195.47065121, + 85753219.1612091, + -36475708.52265885, + 'context', + 26769044.884643376, + 36998590.48639223, + -3788221.7909231335, + ); + console.log(result.data); +})(); + +``` + + + + +## License +License: MIT. See license in LICENSE. + diff --git a/examples/.env.example b/examples/.env.example new file mode 100644 index 00000000..5e4007e8 --- /dev/null +++ b/examples/.env.example @@ -0,0 +1 @@ +PLEXSDK_TOKEN= diff --git a/examples/README.md b/examples/README.md index 07b63439..9f244dcd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,43 +1,23 @@ -# 🧰 Simple TypeScript Starter | 2022 +# @lukehagar/plexjs-example +A basic example of how to use the @lukehagar/plexjs package. -> We talk about a lot of **advanced Node.js and TypeScript** concepts on [the blog](https://khalilstemmler.com), particularly focused around Domain-Driven Design and large-scale enterprise application patterns. However, I received a few emails from readers that were interested in seeing what a basic TypeScript starter project looks like. So I've put together just that. +## Installation -### Features +In the event `@lukehagar/plexjs` is not published to npm, you can install it locally by running the following command in the examples folder: +```sh +npm run setup +``` -- Minimal -- TypeScript v4 -- Testing with Jest -- Linting with Eslint and Prettier -- Pre-commit hooks with Husky -- VS Code debugger scripts -- Local development with Nodemon +This will rebuild the parent package and install it locally. -### Scripts +Otherwise you can install it from npm: +```sh +npm install @lukehagar/plexjs +``` -#### `npm run start:dev` +## Usage -Starts the application in development using `nodemon` and `ts-node` to do hot reloading. - -#### `npm run start` - -Starts the app in production by first building the project with `npm run build`, and then executing the compiled JavaScript at `build/index.js`. - -#### `npm run build` - -Builds the app at `build`, cleaning the folder first. - -#### `npm run test` - -Runs the `jest` tests once. - -#### `npm run test:dev` - -Run the `jest` tests in watch mode, waiting for file changes. - -#### `npm run prettier-format` - -Format your code. - -#### `npm run prettier-watch` - -Format your code in watch mode, waiting for file changes. +To run the example, run the following command in the examples folder: +```sh +npm run start +``` diff --git a/examples/package.json b/examples/package.json index b99e04d2..a1327176 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,31 +1,18 @@ { - "name": "typescript-starter", + "name": "@lukehagar/plexjs-example", "version": "1.0.0", - "description": "A basic typescript app starter for 2023.", - "main": "index.js", - "scripts": { - "build": "rimraf ./build && tsc", - "dev": "npx nodemon", - "start": "npm run build && node build/index.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@lukehagar/plexjs": "^0.0.27", - "@types/jest": "^29.5.3", - "@types/node": "^20.4.2", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "dotenv": "^16.3.1", - "nodemon": "^3.0.1", - "onchange": "^7.1.0", - "rimraf": "^5.0.1", - "run-script-os": "^1.1.6", - "ts-node": "^10.9.1", - "typescript": "^5.1.6" - }, + "private": true, "dependencies": { - "jest-cucumber": "^3.0.1" + "@lukehagar/plexjs": "file:../" + }, + "scripts": { + "setup": "npm --prefix ../ install && npm --prefix ../ run build && npm install", + "start": "tsc && node -r dotenv/config dist/index.js", + "dev": "ts-node src/index.ts" + }, + "devDependencies": { + "ts-node": "^10.9.1", + "typescript": "4.8.4", + "dotenv": "^8.2.0" } } diff --git a/examples/src/index.ts b/examples/src/index.ts index e757e47f..84b31e8c 100644 --- a/examples/src/index.ts +++ b/examples/src/index.ts @@ -1,14 +1,8 @@ -import { Configuration, ServerApi, PlexTvApi } from "@lukehagar/plexjs"; -import dotenv from "dotenv"; -dotenv.config(); +import { PlexSDK } from '@lukehagar/plexjs'; -const config = new Configuration({ - basePath: process.env.BASE_PATH, - plexToken: process.env.PLEX_TOKEN, -}); +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); -new ServerApi(config).getServerCapabilities().then((resp) => console.log(resp)); - -new PlexTvApi(config).getDevices().then((resp) => console.log(resp)); - -new PlexTvApi(config).getUserDetails().then((resp) => console.log(resp)); +(async () => { + const result = await sdk.server.getServerCapabilities(); + console.log(result.data); +})(); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 15b691d6..6cb81edc 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -1,16 +1,103 @@ { "compilerOptions": { - "target": "es5", - "module": "commonjs", - "lib": ["es6"], - "allowJs": true, - "outDir": "build", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "types": ["node", "jest"], - "skipLibCheck": true - }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"] + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } } diff --git a/install.sh b/install.sh new file mode 100644 index 00000000..18856268 --- /dev/null +++ b/install.sh @@ -0,0 +1,2 @@ +npm install +npm run test diff --git a/jest.config.json b/jest.config.json new file mode 100644 index 00000000..2717f589 --- /dev/null +++ b/jest.config.json @@ -0,0 +1,4 @@ +{ + "preset": "ts-jest", + "testEnvironment": "node" +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..1b128c35 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6197 @@ +{ + "name": "@lukehagar/plexjs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@lukehagar/plexjs", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "axios": "^1.5.1" + }, + "devDependencies": { + "@types/jest": "^29.5.6", + "@types/node": "^17.0.23", + "@typescript-eslint/eslint-plugin": "^5.43.0", + "@typescript-eslint/parser": "^5.43.0", + "eslint": "^8.20.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.7.0", + "nock": "^13.2.4", + "prettier": "^2.6.2", + "ts-jest": "^29.1.1", + "typescript": "4.8.4" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", + "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", + "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", + "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", + "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", + "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz", + "integrity": "sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz", + "integrity": "sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", + "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", + "integrity": "sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.2.tgz", + "integrity": "sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.1.tgz", + "integrity": "sha512-IJTNCJMRHfRfb8un89z1QtS0x890C2QUrUxFMK8zy+RizcId6mfnqOf68Bu9YkDgpLYuvCm6aYbwDatXVZPjMQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001554", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", + "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.568", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.568.tgz", + "integrity": "sha512-3TCOv8+BY6Ltpt1/CmGBMups2IdKOyfEmz4J8yIS4xLSeMm0Rf+psSaxLuswG9qMKt+XbNbmADybtXGpTFlbDg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/nock": { + "version": "13.3.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.6.tgz", + "integrity": "sha512-lT6YuktKroUFM+27mubf2uqQZVy2Jf+pfGzuh9N6VwdHlFoZqvi4zyxFTVR1w/ChPqGY6yxGehHp6C3wqCASCw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..0f47eaad --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "source": "./src/index.ts", + "exports": { + "require": "./dist/commonjs/index.js", + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/esm/index.js" + }, + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js", + "browser": "./dist/index.umd.js", + "unpkg": "./dist/index.umd.js", + "types": "./dist/commonjs/index.d.ts", + "files": [ + "dist", + "README.md" + ], + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.43.0", + "@typescript-eslint/parser": "^5.43.0", + "@types/node": "^17.0.23", + "@types/jest": "^29.5.6", + "eslint": "^8.20.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.7.0", + "nock": "^13.2.4", + "prettier": "^2.6.2", + "ts-jest": "^29.1.1", + "typescript": "4.8.4" + }, + "scripts": { + "build": "npm run build:all", + "build:cjs": "tsc --module commonjs --outDir dist/commonjs", + "build:esm": "tsc --module esnext --outDir dist/esm", + "build:umd": "tsc --module umd --outDir dist/umd", + "build:all": "npm run build:cjs && npm run build:esm && npm run build:umd", + "lint": "eslint --ext .ts,.js ./src/ --resolve-plugins-relative-to .", + "lint:ci": "eslint --ext .ts,.js ./src/ --resolve-plugins-relative-to . --cache --quiet", + "lint:fix": "eslint --ext .ts,.js ./src/ --resolve-plugins-relative-to . --cache --fix", + "rebuild": "rm -rf dist/ && tsc", + "test": "jest --detectOpenHandles", + "watch": "rm -rf dist/ && tsc -w", + "version": "tsc --version", + "prepublishOnly": "npm run build" + }, + "name": "@lukehagar/plexjs", + "description": "PlexSDK - An Open API Spec for interacting with Plex.tv and Plex Servers", + "version": "0.0.1", + "author": "Luke Hagar ", + "dependencies": { + "axios": "^1.5.1" + }, + "license": "MIT" +} diff --git a/src/BaseService.ts b/src/BaseService.ts new file mode 100644 index 00000000..ec5b52ad --- /dev/null +++ b/src/BaseService.ts @@ -0,0 +1,47 @@ +import { Environment } from './http/Environment'; +import HTTPLibrary from './http/HTTPLibrary'; +import { Headers } from './http/HTTPClient'; + +export default class BaseService { + public baseUrl: string = Environment.DEFAULT; + + public httpClient = new HTTPLibrary(); + + private apiKey: string = ''; + + private apiKeyHeader: string = 'X-Plex-Token'; + + setApiKey(key: string, header: string = 'X-Plex-Token'): void { + this.apiKey = key; + this.apiKeyHeader = header; + } + + getAuthorizationHeader(): Headers { + const apiKeyAuth = { [this.apiKeyHeader]: this.apiKey }; + + return { ...apiKeyAuth }; + } + + setBaseUrl(url: string): void { + this.baseUrl = url; + } + + constructor(apiKey: string = '', apiKeyHeader: string = 'X-Plex-Token') { + this.setApiKey(apiKey, apiKeyHeader); + } + + static patternMatching(value: string, pattern: string, variableName: string): string { + if (!value) { + throw new Error(`${variableName} cannot be null or undefined`); + } + if (!value.match(new RegExp(pattern))) { + throw new Error(`Invalid value for ${variableName}: must match ${pattern}`); + } + return value; + } + + static urlEncode = (input: { [key: string]: any }): string => + Object.keys(input) + .map((key) => `${key}=${encodeURIComponent(input[key])}`) + .join('&'); +} diff --git a/src/hooks/Hook.ts b/src/hooks/Hook.ts new file mode 100644 index 00000000..443030be --- /dev/null +++ b/src/hooks/Hook.ts @@ -0,0 +1,28 @@ +export interface Request { + method: string; + url: string; + input?: object; + headers: object; +} + +export interface Response { + data: object; + headers: object; + status: number; +} + +export interface Exception extends Error { + title: string; + type?: string; + detail?: string; + instance?: string; + statusCode: number; +} + +export interface Hook { + beforeRequest(request: Request): Promise; + + afterResponse(request: Request, response: Response): Promise; + + onError(error: Exception): Promise; +} diff --git a/src/http/Environment.ts b/src/http/Environment.ts new file mode 100644 index 00000000..b04f40ff --- /dev/null +++ b/src/http/Environment.ts @@ -0,0 +1,3 @@ +export enum Environment { + DEFAULT = '{protocol}://{ip}:{port}', +} diff --git a/src/http/HTTPClient.ts b/src/http/HTTPClient.ts new file mode 100644 index 00000000..2a37df8e --- /dev/null +++ b/src/http/HTTPClient.ts @@ -0,0 +1,12 @@ +export interface Headers extends Record {} + +/** + * Defines the basic operations for an HTTP client. + */ +export default interface HTTPClient { + get(url: string, input: any, headers: Headers, retry?: boolean): Promise; + post(url: string, input: any, headers: Headers, retry?: boolean): Promise; + delete(url: string, input: any, headers: Headers, retry?: boolean): Promise; + put(url: string, input: any, headers: Headers, retry?: boolean): Promise; + patch(url: string, input: any, headers: Headers, retry?: boolean): Promise; +} diff --git a/src/http/HTTPLibrary.ts b/src/http/HTTPLibrary.ts new file mode 100644 index 00000000..121a7c60 --- /dev/null +++ b/src/http/HTTPLibrary.ts @@ -0,0 +1,161 @@ +import axios, { AxiosError } from 'axios'; + +import HTTPClient, { Headers } from './HTTPClient'; +import throwHttpError from './httpExceptions'; + +export default class HTTPLibrary implements HTTPClient { + readonly userAgentHeader: Headers = { + 'User-Agent': 'liblab/0.1.25 PlexSDK/0.0.1 typescript/5.2.2', + }; + + readonly retryAttempts: number = 3; + + readonly retryDelayMs: number = 150; + + private static readonly responseMapper: Map = new Map([ + ['type', 'type_'], + ['default', 'default_'], + ]); + + private readonly requestMapper: Map = new Map([ + ['type_', 'type'], + ['default_', 'default'], + ]); + + async get(url: string, input: any, headers: Headers, retry: boolean = false): Promise { + const request = () => + axios.get(url, { + headers: { ...headers, ...this.getUserAgentHeader() }, + data: + Object.keys(input).length > 0 + ? HTTPLibrary.convertKeysWithMapper(input, this.requestMapper) + : undefined, + }); + + const response = retry + ? await this.retry(this.retryAttempts, request, this.retryDelayMs) + : await request(); + return HTTPLibrary.handleResponse(response); + } + + async post(url: string, input: any, headers: Headers, retry: boolean = false): Promise { + const request = () => + axios.post(url, HTTPLibrary.convertKeysWithMapper(input, this.requestMapper), { + headers: { ...headers, ...this.getUserAgentHeader() }, + }); + + const response = retry + ? await this.retry(this.retryAttempts, request, this.retryDelayMs) + : await request(); + + return HTTPLibrary.handleResponse(response); + } + + async delete(url: string, input: any, headers: Headers, retry: boolean = false): Promise { + const request = () => + axios.delete(url, { + headers: { ...headers, ...this.getUserAgentHeader() }, + data: HTTPLibrary.convertKeysWithMapper(input, this.requestMapper), + }); + + const response = retry + ? await this.retry(this.retryAttempts, request, this.retryDelayMs) + : await request(); + + return HTTPLibrary.handleResponse(response); + } + + async put(url: string, input: any, headers: Headers, retry: boolean = false): Promise { + const request = () => + axios.put(url, HTTPLibrary.convertKeysWithMapper(input, this.requestMapper), { + headers: { ...headers, ...this.getUserAgentHeader() }, + }); + + const response = retry + ? await this.retry(this.retryAttempts, request, this.retryDelayMs) + : await request(); + + return HTTPLibrary.handleResponse(response); + } + + async patch(url: string, input: any, headers: Headers, retry: boolean = false): Promise { + const request = () => + axios.patch(url, HTTPLibrary.convertKeysWithMapper(input, this.requestMapper), { + headers: { ...headers, ...this.getUserAgentHeader() }, + }); + + const response = retry + ? await this.retry(this.retryAttempts, request, this.retryDelayMs) + : await request(); + + return HTTPLibrary.handleResponse(response); + } + + async retry(retries: number, callbackFn: () => any, delay: number): Promise { + let result: any; + + try { + result = await callbackFn(); + } catch (e: any) { + if ((e as AxiosError).isAxiosError) { + if (e.response) { + if (![500, 503, 504].includes(e.response.status)) { + return e.response; + } + } + } + if (retries > 1) { + // eslint-disable-next-line no-promise-executor-return + await new Promise((resolve) => setTimeout(resolve, delay)); + result = await this.retry(retries - 1, callbackFn, delay * 2); + } else { + throw e; + } + } + + return result; + } + + private static handleResponse(response: any) { + if (response.status >= 400) { + throwHttpError(response); + } + + response.data = HTTPLibrary.convertKeysWithMapper(response.data, this.responseMapper); + + return response; + } + + private getUserAgentHeader(): Headers { + if (typeof window !== 'undefined') { + return {}; + } + return this.userAgentHeader; + } + + /** + *Converts keys in an object using a provided JSON mapper. + * @param {any} obj - The object to convert keys for. + * @param {Object} jsonMapper - The JSON mapper containing key mappings. + * @returns {any} - The object with converted keys. + */ + private static convertKeysWithMapper(obj: T, jsonMapper: Map): any { + if (!obj || typeof obj !== 'object') { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => HTTPLibrary.convertKeysWithMapper(item, jsonMapper)); + } + + const convertedObj: Record = {}; + Object.entries(obj).forEach(([key, value]) => { + if (value !== undefined) { + const convertedKey = jsonMapper.get(key) || key; + convertedObj[convertedKey] = HTTPLibrary.convertKeysWithMapper(value, jsonMapper); + } + }); + + return convertedObj; + } +} diff --git a/src/http/QuerySerializer.ts b/src/http/QuerySerializer.ts new file mode 100644 index 00000000..7803158f --- /dev/null +++ b/src/http/QuerySerializer.ts @@ -0,0 +1,82 @@ +export type Explode = boolean; +export type QueryStyles = 'form' | 'spaceDelimited' | 'pipeDelimited' | 'deepObject'; +export type PathStyles = 'simple' | 'label' | 'matrix'; + +const styleMethods: Record = { + simple: (value: unknown, explode: boolean) => { + // Check if the value is an array + if (Array.isArray(value)) { + return explode ? value.join(',') : value.join(); + } + + // Check if the value is an object + if (typeof value === 'object' && value !== null) { + if (explode) { + // Serialize object with exploded format: "key=value,key2=value2" + return Object.entries(value) + .map(([parameterName, parameterValue]) => `${parameterName}=${parameterValue}`) + .join(','); + } + // Serialize object with non-exploded format: "key,value,key2,value2" + return Object.entries(value) + .flatMap(([parameterName, parameterValue]) => [parameterName, parameterValue]) + .join(','); + } + + // For primitive values + return String(value); + }, + + form: (parameterName: string, parameterValue: unknown, explode: boolean) => { + // Check if the parameterValue is an array + if (Array.isArray(parameterValue)) { + return explode + ? parameterValue.map((value) => `${parameterName}=${value}`).join('&') + : `${parameterName}=${parameterValue.join(',')}`; + } + + // Check if the parameterValue is an object + if (typeof parameterValue === 'object' && parameterValue !== null) { + if (explode) { + // Serialize object with exploded format: "key1=value1&key2=value2" + return Object.entries(parameterValue) + .map(([name, value]) => `${name}=${value}`) + .join('&'); + } + // Serialize object with non-exploded format: "key=key1,value1,key2,value2" + return `${parameterName}=${Object.entries(parameterValue) + .flatMap(([name, value]) => [name, value]) + .join(',')}`; + } + + // For primitive values + return `${parameterName}=${parameterValue}`; + }, +}; + +export function serializeQuery( + style: QueryStyles, + explode: Explode, + key: string, + value: unknown, +): string { + const method = styleMethods[style]; + if (!method) return ''; + return method(key, value, explode); +} + +export function serializePath( + style: PathStyles, + explode: Explode, + value: unknown, + key?: string, +): string { + const method = styleMethods[style]; + if (!method) return ''; + // The `simple` and `label` styles do not require a `key` + if (!key) { + return method(value, explode); + } else { + return method(key, value, explode); + } +} diff --git a/src/http/Response.ts b/src/http/Response.ts new file mode 100644 index 00000000..37be3fda --- /dev/null +++ b/src/http/Response.ts @@ -0,0 +1,4 @@ +export default interface Response { + data: T; + headers: Record; +} diff --git a/src/http/errors/BadGateway.ts b/src/http/errors/BadGateway.ts new file mode 100644 index 00000000..9c6d289c --- /dev/null +++ b/src/http/errors/BadGateway.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class BadGateway extends BaseHTTPError { + statusCode = 502; + + title = 'Bad Gateway'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/BadRequest.ts b/src/http/errors/BadRequest.ts new file mode 100644 index 00000000..7df6dd0e --- /dev/null +++ b/src/http/errors/BadRequest.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class BadRequest extends BaseHTTPError { + statusCode = 400; + + title = 'Bad Request'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/Conflict.ts b/src/http/errors/Conflict.ts new file mode 100644 index 00000000..9f5a7508 --- /dev/null +++ b/src/http/errors/Conflict.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class Conflict extends BaseHTTPError { + statusCode = 409; + + title = 'Conflict'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/ExpectationFailed.ts b/src/http/errors/ExpectationFailed.ts new file mode 100644 index 00000000..22299f5b --- /dev/null +++ b/src/http/errors/ExpectationFailed.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class ExpectationFailed extends BaseHTTPError { + statusCode = 417; + + title = 'Expectation Failed'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/FailedDependency.ts b/src/http/errors/FailedDependency.ts new file mode 100644 index 00000000..ea99d2ca --- /dev/null +++ b/src/http/errors/FailedDependency.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class FailedDependency extends BaseHTTPError { + statusCode = 424; + + title = 'Failed Dependency'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/Forbidden.ts b/src/http/errors/Forbidden.ts new file mode 100644 index 00000000..82ee9347 --- /dev/null +++ b/src/http/errors/Forbidden.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class Forbidden extends BaseHTTPError { + statusCode = 403; + + title = 'Forbidden'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/GatewayTimeout.ts b/src/http/errors/GatewayTimeout.ts new file mode 100644 index 00000000..daa7af67 --- /dev/null +++ b/src/http/errors/GatewayTimeout.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class GatewayTimeout extends BaseHTTPError { + statusCode = 504; + + title = 'Gateway Timeout'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/Gone.ts b/src/http/errors/Gone.ts new file mode 100644 index 00000000..a113c4d3 --- /dev/null +++ b/src/http/errors/Gone.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class Gone extends BaseHTTPError { + statusCode = 410; + + title = 'Gone'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/HttpVersionNotSupported.ts b/src/http/errors/HttpVersionNotSupported.ts new file mode 100644 index 00000000..4fb6f468 --- /dev/null +++ b/src/http/errors/HttpVersionNotSupported.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class HttpVersionNotSupported extends BaseHTTPError { + statusCode = 505; + + title = 'HTTP Version Not Supported'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/InternalServerError.ts b/src/http/errors/InternalServerError.ts new file mode 100644 index 00000000..a06205a7 --- /dev/null +++ b/src/http/errors/InternalServerError.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class InternalServerError extends BaseHTTPError { + statusCode = 500; + + title = 'Internal Server Error'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/LengthRequired.ts b/src/http/errors/LengthRequired.ts new file mode 100644 index 00000000..5fe59484 --- /dev/null +++ b/src/http/errors/LengthRequired.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class LengthRequired extends BaseHTTPError { + statusCode = 411; + + title = 'LengthRequired'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/Locked.ts b/src/http/errors/Locked.ts new file mode 100644 index 00000000..7ef8fd9a --- /dev/null +++ b/src/http/errors/Locked.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class Locked extends BaseHTTPError { + statusCode = 423; + + title = 'Locked'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/LoopDetected.ts b/src/http/errors/LoopDetected.ts new file mode 100644 index 00000000..dfb598a8 --- /dev/null +++ b/src/http/errors/LoopDetected.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class LoopDetected extends BaseHTTPError { + statusCode = 508; + + title = 'Loop Detected'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/MethodNotAllowed.ts b/src/http/errors/MethodNotAllowed.ts new file mode 100644 index 00000000..870e5c32 --- /dev/null +++ b/src/http/errors/MethodNotAllowed.ts @@ -0,0 +1,14 @@ +import { BaseHTTPError } from './base'; + +export default class MethodNotAllowed extends BaseHTTPError { + statusCode = 405; + + title = 'Method Not Allowed'; + + allow?: string[]; + + constructor(detail: string = '', allow?: string[]) { + super(detail); + this.allow = allow; + } +} diff --git a/src/http/errors/MisdirectedRequest.ts b/src/http/errors/MisdirectedRequest.ts new file mode 100644 index 00000000..f00f2124 --- /dev/null +++ b/src/http/errors/MisdirectedRequest.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class MisdirectedRequest extends BaseHTTPError { + statusCode = 421; + + title = 'Misdirected Request'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/NetworkAuthenticationRequired.ts b/src/http/errors/NetworkAuthenticationRequired.ts new file mode 100644 index 00000000..29267f39 --- /dev/null +++ b/src/http/errors/NetworkAuthenticationRequired.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class NetworkAuthenticationRequired extends BaseHTTPError { + statusCode = 511; + + title = 'Network Authentication Required'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/NotAcceptable.ts b/src/http/errors/NotAcceptable.ts new file mode 100644 index 00000000..c2ad2e27 --- /dev/null +++ b/src/http/errors/NotAcceptable.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class NotAcceptable extends BaseHTTPError { + statusCode = 406; + + title = 'Not Acceptable'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/NotExtended.ts b/src/http/errors/NotExtended.ts new file mode 100644 index 00000000..a1378340 --- /dev/null +++ b/src/http/errors/NotExtended.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class NotExtended extends BaseHTTPError { + statusCode = 510; + + title = 'Not Extended'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/NotFound.ts b/src/http/errors/NotFound.ts new file mode 100644 index 00000000..0df322b8 --- /dev/null +++ b/src/http/errors/NotFound.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class NotFound extends BaseHTTPError { + statusCode = 404; + + title = 'Not Found'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/NotImplemented.ts b/src/http/errors/NotImplemented.ts new file mode 100644 index 00000000..0e9e9e96 --- /dev/null +++ b/src/http/errors/NotImplemented.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class NotImplemented extends BaseHTTPError { + statusCode = 501; + + title = 'Not Implemented'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/PayloadTooLarge.ts b/src/http/errors/PayloadTooLarge.ts new file mode 100644 index 00000000..ca40131e --- /dev/null +++ b/src/http/errors/PayloadTooLarge.ts @@ -0,0 +1,14 @@ +import { BaseHTTPError } from './base'; + +export default class PayloadTooLarge extends BaseHTTPError { + statusCode = 413; + + title = 'Payload Too Large'; + + retryAfter: number | null; + + constructor(detail: string = '', retryAfter: number | null = null) { + super(detail); + this.retryAfter = retryAfter; + } +} diff --git a/src/http/errors/PaymentRequired.ts b/src/http/errors/PaymentRequired.ts new file mode 100644 index 00000000..abbabbb8 --- /dev/null +++ b/src/http/errors/PaymentRequired.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class PaymentRequired extends BaseHTTPError { + statusCode = 402; + + title = 'Payment Required'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/PreconditionFailed.ts b/src/http/errors/PreconditionFailed.ts new file mode 100644 index 00000000..2e0b8f95 --- /dev/null +++ b/src/http/errors/PreconditionFailed.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class PreconditionFailed extends BaseHTTPError { + statusCode = 412; + + title = 'PreconditionFailed'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/PreconditionRequired.ts b/src/http/errors/PreconditionRequired.ts new file mode 100644 index 00000000..9d7b83ee --- /dev/null +++ b/src/http/errors/PreconditionRequired.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class PreconditionRequired extends BaseHTTPError { + statusCode = 428; + + title = 'Precondition Required'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/ProxyAuthenticationRequired.ts b/src/http/errors/ProxyAuthenticationRequired.ts new file mode 100644 index 00000000..26e1bd51 --- /dev/null +++ b/src/http/errors/ProxyAuthenticationRequired.ts @@ -0,0 +1,14 @@ +import { AuthenticateChallenge, BaseHTTPError } from './base'; + +export default class ProxyAuthenticationRequired extends BaseHTTPError { + statusCode = 407; + + title = 'Proxy Authentication Required'; + + proxyAuthenticate?: AuthenticateChallenge; + + constructor(detail: string = '', proxyAuthenticate?: AuthenticateChallenge) { + super(detail); + this.proxyAuthenticate = proxyAuthenticate; + } +} diff --git a/src/http/errors/RangeNotSatisfiable.ts b/src/http/errors/RangeNotSatisfiable.ts new file mode 100644 index 00000000..704e4ec0 --- /dev/null +++ b/src/http/errors/RangeNotSatisfiable.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class RangeNotSatisfiable extends BaseHTTPError { + statusCode = 416; + + title = 'Range Not Satisfiable'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/RequestHeaderFieldsTooLarge.ts b/src/http/errors/RequestHeaderFieldsTooLarge.ts new file mode 100644 index 00000000..4a34fb08 --- /dev/null +++ b/src/http/errors/RequestHeaderFieldsTooLarge.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class RequestHeaderFieldsTooLarge extends BaseHTTPError { + statusCode = 431; + + title = 'Request Header Fields Too Large'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/RequestTimeout.ts b/src/http/errors/RequestTimeout.ts new file mode 100644 index 00000000..ba1a5c3a --- /dev/null +++ b/src/http/errors/RequestTimeout.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class RequestTimeout extends BaseHTTPError { + statusCode = 408; + + title = 'Request Timeout'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/ServiceUnavailable.ts b/src/http/errors/ServiceUnavailable.ts new file mode 100644 index 00000000..8cd62e80 --- /dev/null +++ b/src/http/errors/ServiceUnavailable.ts @@ -0,0 +1,14 @@ +import { BaseHTTPError } from './base'; + +export default class ServiceUnavailable extends BaseHTTPError { + statusCode = 503; + + title = 'Service Unavailable'; + + retryAfter: number | null; + + constructor(detail: string = '', retryAfter: number | null = null) { + super(detail); + this.retryAfter = retryAfter; + } +} diff --git a/src/http/errors/TooEarly.ts b/src/http/errors/TooEarly.ts new file mode 100644 index 00000000..b7f99c63 --- /dev/null +++ b/src/http/errors/TooEarly.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class TooEarly extends BaseHTTPError { + statusCode = 425; + + title = 'Too Early'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/TooManyRequests.ts b/src/http/errors/TooManyRequests.ts new file mode 100644 index 00000000..eb5280d5 --- /dev/null +++ b/src/http/errors/TooManyRequests.ts @@ -0,0 +1,14 @@ +import { BaseHTTPError } from './base'; + +export default class TooManyRequests extends BaseHTTPError { + statusCode = 429; + + title = 'Too Many Requests'; + + retryAfter: number | null; + + constructor(detail: string = '', retryAfter: number | null = null) { + super(detail); + this.retryAfter = retryAfter; + } +} diff --git a/src/http/errors/Unauthorized.ts b/src/http/errors/Unauthorized.ts new file mode 100644 index 00000000..15da735b --- /dev/null +++ b/src/http/errors/Unauthorized.ts @@ -0,0 +1,14 @@ +import { AuthenticateChallenge, BaseHTTPError } from './base'; + +export default class Unauthorized extends BaseHTTPError { + statusCode = 401; + + title = 'Unauthorized'; + + wwwAuthenticate?: AuthenticateChallenge; + + constructor(detail: string = '', wwwAuthenticate?: AuthenticateChallenge) { + super(detail); + this.wwwAuthenticate = wwwAuthenticate; + } +} diff --git a/src/http/errors/UnavailableForLegalReasons.ts b/src/http/errors/UnavailableForLegalReasons.ts new file mode 100644 index 00000000..70e6d073 --- /dev/null +++ b/src/http/errors/UnavailableForLegalReasons.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class UnavailableForLegalReasons extends BaseHTTPError { + statusCode = 451; + + title = 'Unavailable For Legal Reasons'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/UnprocessableEntity.ts b/src/http/errors/UnprocessableEntity.ts new file mode 100644 index 00000000..70000c19 --- /dev/null +++ b/src/http/errors/UnprocessableEntity.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class UnprocessableEntity extends BaseHTTPError { + statusCode = 422; + + title = 'Unprocessable Entity'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/UnsufficientStorage.ts b/src/http/errors/UnsufficientStorage.ts new file mode 100644 index 00000000..e61e12c3 --- /dev/null +++ b/src/http/errors/UnsufficientStorage.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class UnsufficientStorage extends BaseHTTPError { + statusCode = 507; + + title = 'Unsufficient Storage'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/UnsupportedMediaType.ts b/src/http/errors/UnsupportedMediaType.ts new file mode 100644 index 00000000..a27f2428 --- /dev/null +++ b/src/http/errors/UnsupportedMediaType.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class UnsupportedMediaType extends BaseHTTPError { + statusCode = 415; + + title = 'Unsupported Media Type'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/UpgradeRequired.ts b/src/http/errors/UpgradeRequired.ts new file mode 100644 index 00000000..0ac17436 --- /dev/null +++ b/src/http/errors/UpgradeRequired.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class UpgradeRequired extends BaseHTTPError { + statusCode = 426; + + title = 'Upgrade Required'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/UriTooLong.ts b/src/http/errors/UriTooLong.ts new file mode 100644 index 00000000..56eca8b7 --- /dev/null +++ b/src/http/errors/UriTooLong.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class UriTooLong extends BaseHTTPError { + statusCode = 414; + + title = 'URI Too Long'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/VariantAlsoNegotiates.ts b/src/http/errors/VariantAlsoNegotiates.ts new file mode 100644 index 00000000..079df291 --- /dev/null +++ b/src/http/errors/VariantAlsoNegotiates.ts @@ -0,0 +1,11 @@ +import { BaseHTTPError } from './base'; + +export default class VariantAlsoNegotiates extends BaseHTTPError { + statusCode = 506; + + title = 'Variant Also Negotiates'; + + constructor(detail: string = '') { + super(detail); + } +} diff --git a/src/http/errors/base.ts b/src/http/errors/base.ts new file mode 100644 index 00000000..f5ebd46f --- /dev/null +++ b/src/http/errors/base.ts @@ -0,0 +1,52 @@ +export interface IHTTPError extends Error { + statusCode: number; +} + +export interface IHTTPErrorDescription extends IHTTPError { + type?: string; + title: string; + detail?: string; + instance?: string; +} + +export function isHTTPError(error: unknown): error is IHTTPError { + if (!error) { + return false; + } + return Number.isInteger((error as IHTTPError).statusCode); +} + +export function isHTTPIssue(error: unknown): error is IHTTPErrorDescription { + if (!error) { + return false; + } + return (error as IHTTPErrorDescription).title !== undefined && isHTTPError(error); +} + +export class BaseHTTPError extends Error implements IHTTPError { + public type?: string; + + public title: string = 'Internal Server Error'; + + public detail?: string; + + public instance?: string; + + public statusCode: number = 500; + + constructor(detail: string = '') { + super(detail || 'An Unknown HTTP Error Occurred'); + this.detail = detail; + this.stack = (new Error()).stack; + } +} + +export function isClientError(error: Error): boolean { + return isHTTPError(error); +} + +export function isServerError(e: Error): boolean { + return isHTTPError(e) && e.statusCode >= 500 && e.statusCode <= 599; +} + +export type AuthenticateChallenge = string | string[]; diff --git a/src/http/errors/index.ts b/src/http/errors/index.ts new file mode 100644 index 00000000..0f98f742 --- /dev/null +++ b/src/http/errors/index.ts @@ -0,0 +1,83 @@ +import BadRequest from './BadRequest'; +import Unauthorized from './Unauthorized'; +import PaymentRequired from './PaymentRequired'; +import Forbidden from './Forbidden'; +import NotFound from './NotFound'; +import MethodNotAllowed from './MethodNotAllowed'; +import NotAcceptable from './NotAcceptable'; +import ProxyAuthenticationRequired from './ProxyAuthenticationRequired'; +import RequestTimeout from './RequestTimeout'; +import Conflict from './Conflict'; +import Gone from './Gone'; +import LengthRequired from './LengthRequired'; +import PreconditionFailed from './PreconditionFailed'; +import PayloadTooLarge from './PayloadTooLarge'; +import UriTooLong from './UriTooLong'; +import UnsupportedMediaType from './UnsupportedMediaType'; +import RangeNotSatisfiable from './RangeNotSatisfiable'; +import ExpectationFailed from './ExpectationFailed'; +import MisdirectedRequest from './MisdirectedRequest'; +import UnprocessableEntity from './UnprocessableEntity'; +import Locked from './Locked'; +import FailedDependency from './FailedDependency'; +import TooEarly from './TooEarly'; +import UpgradeRequired from './UpgradeRequired'; +import PreconditionRequired from './PreconditionRequired'; +import TooManyRequests from './TooManyRequests'; +import RequestHeaderFieldsTooLarge from './RequestHeaderFieldsTooLarge'; +import UnavailableForLegalReasons from './UnavailableForLegalReasons'; +import InternalServerError from './InternalServerError'; +import NotImplemented from './NotImplemented'; +import BadGateway from './BadGateway'; +import ServiceUnavailable from './ServiceUnavailable'; +import GatewayTimeout from './GatewayTimeout'; +import HttpVersionNotSupported from './HttpVersionNotSupported'; +import VariantAlsoNegotiates from './VariantAlsoNegotiates'; +import UnsufficientStorage from './UnsufficientStorage'; +import LoopDetected from './LoopDetected'; +import NotExtended from './NotExtended'; +import NetworkAuthenticationRequired from './NetworkAuthenticationRequired'; +import { BaseHTTPError } from './base'; + +export { + BaseHTTPError, + BadRequest, + Unauthorized, + PaymentRequired, + Forbidden, + NotFound, + MethodNotAllowed, + NotAcceptable, + ProxyAuthenticationRequired, + RequestTimeout, + Conflict, + Gone, + LengthRequired, + PreconditionFailed, + PayloadTooLarge, + UriTooLong, + UnsupportedMediaType, + RangeNotSatisfiable, + ExpectationFailed, + MisdirectedRequest, + UnprocessableEntity, + Locked, + FailedDependency, + TooEarly, + UpgradeRequired, + PreconditionRequired, + TooManyRequests, + RequestHeaderFieldsTooLarge, + UnavailableForLegalReasons, + InternalServerError, + NotImplemented, + BadGateway, + ServiceUnavailable, + GatewayTimeout, + HttpVersionNotSupported, + VariantAlsoNegotiates, + UnsufficientStorage, + LoopDetected, + NotExtended, + NetworkAuthenticationRequired, +}; diff --git a/src/http/httpExceptions.ts b/src/http/httpExceptions.ts new file mode 100644 index 00000000..45c8fe16 --- /dev/null +++ b/src/http/httpExceptions.ts @@ -0,0 +1,132 @@ +import { + BaseHTTPError, + BadRequest, + Unauthorized, + PaymentRequired, + Forbidden, + NotFound, + MethodNotAllowed, + NotAcceptable, + ProxyAuthenticationRequired, + RequestTimeout, + Conflict, + Gone, + LengthRequired, + PreconditionFailed, + PayloadTooLarge, + UriTooLong, + UnsupportedMediaType, + RangeNotSatisfiable, + ExpectationFailed, + MisdirectedRequest, + UnprocessableEntity, + Locked, + FailedDependency, + TooEarly, + UpgradeRequired, + PreconditionRequired, + TooManyRequests, + RequestHeaderFieldsTooLarge, + UnavailableForLegalReasons, + InternalServerError, + NotImplemented, + BadGateway, + ServiceUnavailable, + GatewayTimeout, + HttpVersionNotSupported, + VariantAlsoNegotiates, + UnsufficientStorage, + LoopDetected, + NotExtended, + NetworkAuthenticationRequired, +} from './errors'; + +interface HttpResponseWithError { + status: number; + headers: any; + data?: any; +} + +interface NumberToClass { + [key: number]: any; +} + +const statusCodeToErrorFunction: NumberToClass = { + 400: BadRequest, + 401: Unauthorized, + 402: PaymentRequired, + 403: Forbidden, + 404: NotFound, + 405: MethodNotAllowed, + 406: NotAcceptable, + 407: ProxyAuthenticationRequired, + 408: RequestTimeout, + 409: Conflict, + 410: Gone, + 411: LengthRequired, + 412: PreconditionFailed, + 413: PayloadTooLarge, + 414: UriTooLong, + 415: UnsupportedMediaType, + 416: RangeNotSatisfiable, + 417: ExpectationFailed, + 421: MisdirectedRequest, + 422: UnprocessableEntity, + 423: Locked, + 424: FailedDependency, + 425: TooEarly, + 426: UpgradeRequired, + 428: PreconditionRequired, + 429: TooManyRequests, + 431: RequestHeaderFieldsTooLarge, + 451: UnavailableForLegalReasons, + 500: InternalServerError, + 501: NotImplemented, + 502: BadGateway, + 503: ServiceUnavailable, + 504: GatewayTimeout, + 505: HttpVersionNotSupported, + 506: VariantAlsoNegotiates, + 507: UnsufficientStorage, + 508: LoopDetected, + 510: NotExtended, + 511: NetworkAuthenticationRequired, +}; + +/** + * @summary This function will throw an error. + * + * @param {HttpResponseWithError} response - the response from a request, must contain a status and data fields + * @throws {Error} - an http error + */ +export default function throwHttpError(response: HttpResponseWithError): never { + let error: BaseHTTPError = new BaseHTTPError(response.data); + switch (response.status) { + case 401: + error = new Unauthorized(response.data, response.headers['WWW-Authenticate']); + case 405: + // this indicates a bug in the spec if it allows a method that the server rejects + error = new MethodNotAllowed(response.data, response.headers.allowed); + case 407: + error = new ProxyAuthenticationRequired( + response.data, + response.headers['Proxy-Authenticate'], + ); + case 413: + error = new PayloadTooLarge(response.data, response.headers['Retry-After']); + case 429: + error = new TooManyRequests(response.data, response.headers['Retry-After']); + case 503: + error = new ServiceUnavailable(response.data, response.headers['Retry-After']); + default: + if (response.status in statusCodeToErrorFunction) { + error = new statusCodeToErrorFunction[response.status](response.data); + } else { + const error = new BaseHTTPError(response.data); + error.statusCode = response.status; + error.title = 'unknown error'; + } + } + + throw error; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..6a37e864 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,99 @@ +import { ActivitiesService } from './services/activities/Activities'; +import { ButlerService } from './services/butler/Butler'; +import { HubsService } from './services/hubs/Hubs'; +import { LibraryService } from './services/library/Library'; +import { LogService } from './services/log/Log'; +import { MediaService } from './services/media/Media'; +import { PlaylistsService } from './services/playlists/Playlists'; +import { SearchService } from './services/search/Search'; +import { SecurityService } from './services/security/Security'; +import { ServerService } from './services/server/Server'; +import { SessionsService } from './services/sessions/Sessions'; +import { UpdaterService } from './services/updater/Updater'; +import { VideoService } from './services/video/Video'; + +export * from './models'; + +export * as ActivitiesModels from './services/activities'; +export * as ButlerModels from './services/butler'; +export * as HubsModels from './services/hubs'; +export * as LibraryModels from './services/library'; +export * as LogModels from './services/log'; +export * as PlaylistsModels from './services/playlists'; +export * as SearchModels from './services/search'; +export * as SecurityModels from './services/security'; +export * as ServerModels from './services/server'; +export * as SessionsModels from './services/sessions'; +export * as UpdaterModels from './services/updater'; +export * as VideoModels from './services/video'; + +type Config = { + apiKey?: string; + apiKeyHeader?: string; +}; + +export * from './http/errors'; + +export class PlexSDK { + public activities: ActivitiesService; + public butler: ButlerService; + public hubs: HubsService; + public library: LibraryService; + public log: LogService; + public media: MediaService; + public playlists: PlaylistsService; + public search: SearchService; + public security: SecurityService; + public server: ServerService; + public sessions: SessionsService; + public updater: UpdaterService; + public video: VideoService; + + constructor({ apiKey = '', apiKeyHeader = 'X-Plex-Token' }: Config) { + this.activities = new ActivitiesService(apiKey, apiKeyHeader); + this.butler = new ButlerService(apiKey, apiKeyHeader); + this.hubs = new HubsService(apiKey, apiKeyHeader); + this.library = new LibraryService(apiKey, apiKeyHeader); + this.log = new LogService(apiKey, apiKeyHeader); + this.media = new MediaService(apiKey, apiKeyHeader); + this.playlists = new PlaylistsService(apiKey, apiKeyHeader); + this.search = new SearchService(apiKey, apiKeyHeader); + this.security = new SecurityService(apiKey, apiKeyHeader); + this.server = new ServerService(apiKey, apiKeyHeader); + this.sessions = new SessionsService(apiKey, apiKeyHeader); + this.updater = new UpdaterService(apiKey, apiKeyHeader); + this.video = new VideoService(apiKey, apiKeyHeader); + } + + setBaseUrl(url: string): void { + this.activities.setBaseUrl(url); + this.butler.setBaseUrl(url); + this.hubs.setBaseUrl(url); + this.library.setBaseUrl(url); + this.log.setBaseUrl(url); + this.media.setBaseUrl(url); + this.playlists.setBaseUrl(url); + this.search.setBaseUrl(url); + this.security.setBaseUrl(url); + this.server.setBaseUrl(url); + this.sessions.setBaseUrl(url); + this.updater.setBaseUrl(url); + this.video.setBaseUrl(url); + } + + setApiKey(key: string, header: string = 'X-Plex-Token') { + this.activities.setApiKey(key, header); + this.butler.setApiKey(key, header); + this.hubs.setApiKey(key, header); + this.library.setApiKey(key, header); + this.log.setApiKey(key, header); + this.media.setApiKey(key, header); + this.playlists.setApiKey(key, header); + this.search.setApiKey(key, header); + this.security.setApiKey(key, header); + this.server.setApiKey(key, header); + this.sessions.setApiKey(key, header); + this.updater.setApiKey(key, header); + this.video.setApiKey(key, header); + } +} diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 00000000..5957fb27 --- /dev/null +++ b/src/models.ts @@ -0,0 +1,28 @@ +export type { Download } from './services/updater/models/Download'; +export type { Force } from './services/playlists/models/Force'; +export type { GetAvailableClientsResponse } from './services/server/models/GetAvailableClientsResponse'; +export type { GetButlerTasksResponse } from './services/butler/models/GetButlerTasksResponse'; +export type { GetDevicesResponse } from './services/server/models/GetDevicesResponse'; +export type { GetMyPlexAccountResponse } from './services/server/models/GetMyPlexAccountResponse'; +export type { GetOnDeckResponse } from './services/library/models/GetOnDeckResponse'; +export type { GetRecentlyAddedResponse } from './services/library/models/GetRecentlyAddedResponse'; +export type { GetSearchResultsResponse } from './services/search/models/GetSearchResultsResponse'; +export type { GetServerActivitiesResponse } from './services/activities/models/GetServerActivitiesResponse'; +export type { GetServerCapabilitiesResponse } from './services/server/models/GetServerCapabilitiesResponse'; +export type { GetServerIdentityResponse } from './services/server/models/GetServerIdentityResponse'; +export type { GetServerListResponse } from './services/server/models/GetServerListResponse'; +export type { GetTranscodeSessionsResponse } from './services/sessions/models/GetTranscodeSessionsResponse'; +export type { IncludeDetails } from './services/library/models/IncludeDetails'; +export type { Level } from './services/log/models/Level'; +export type { MinSize } from './services/server/models/MinSize'; +export type { OnlyTransient } from './services/hubs/models/OnlyTransient'; +export type { PlaylistType } from './services/playlists/models/PlaylistType'; +export type { Scope } from './services/security/models/Scope'; +export type { SecurityType } from './services/security/models/SecurityType'; +export type { Skip } from './services/updater/models/Skip'; +export type { Smart } from './services/playlists/models/Smart'; +export type { State } from './services/video/models/State'; +export type { TaskName } from './services/butler/models/TaskName'; +export type { Tonight } from './services/updater/models/Tonight'; +export type { Type } from './services/playlists/models/Type'; +export type { Upscale } from './services/server/models/Upscale'; diff --git a/src/services/README.md b/src/services/README.md new file mode 100644 index 00000000..1128bc71 --- /dev/null +++ b/src/services/README.md @@ -0,0 +1,1977 @@ +# PlexSDK Services +A list of all services and services methods. +- Services + + - [Server](#server) + + - [Media](#media) + + - [Activities](#activities) + + - [Butler](#butler) + + - [Hubs](#hubs) + + - [Search](#search) + + - [Library](#library) + + - [Log](#log) + + - [Playlists](#playlists) + + - [Security](#security) + + - [Sessions](#sessions) + + - [Updater](#updater) + + - [Video](#video) +- [All Methods](#all-methods) + + +## Server + +| Method | Description| +| :-------- | :----------| +| [getServerCapabilities](#getservercapabilities) | Server Capabilities | +| [getServerPreferences](#getserverpreferences) | Get Server Preferences | +| [getAvailableClients](#getavailableclients) | Get Available Clients | +| [getDevices](#getdevices) | Get Devices | +| [getServerIdentity](#getserveridentity) | Get Server Identity | +| [getMyPlexAccount](#getmyplexaccount) | Get MyPlex Account | +| [getResizedPhoto](#getresizedphoto) | Get a Resized Photo | +| [getServerList](#getserverlist) | Get Server List | + + +## Media + +| Method | Description| +| :-------- | :----------| +| [markPlayed](#markplayed) | Mark Media Played | +| [markUnplayed](#markunplayed) | Mark Media Unplayed | +| [updatePlayProgress](#updateplayprogress) | Update Media Play Progress | + + +## Activities + +| Method | Description| +| :-------- | :----------| +| [getServerActivities](#getserveractivities) | Get Server Activities | +| [cancelServerActivities](#cancelserveractivities) | Cancel Server Activities | + + +## Butler + +| Method | Description| +| :-------- | :----------| +| [startAllTasks](#startalltasks) | Start all Butler tasks | +| [getButlerTasks](#getbutlertasks) | Get Butler tasks | +| [stopAllTasks](#stopalltasks) | Stop all Butler tasks | +| [startTask](#starttask) | Start a single Butler task | +| [stopTask](#stoptask) | Stop a single Butler task | + + +## Hubs + +| Method | Description| +| :-------- | :----------| +| [getGlobalHubs](#getglobalhubs) | Get Global Hubs | +| [getLibraryHubs](#getlibraryhubs) | Get library specific hubs | + + +## Search + +| Method | Description| +| :-------- | :----------| +| [performSearch](#performsearch) | Perform a search | +| [performVoiceSearch](#performvoicesearch) | Perform a voice search | +| [getSearchResults](#getsearchresults) | Get Search Results | + + +## Library + +| Method | Description| +| :-------- | :----------| +| [getFileHash](#getfilehash) | Get Hash Value | +| [getRecentlyAdded](#getrecentlyadded) | Get Recently Added | +| [getLibraries](#getlibraries) | Get All Libraries | +| [getLibrary](#getlibrary) | Get Library Details | +| [deleteLibrary](#deletelibrary) | Delete Library Section | +| [getLibraryItems](#getlibraryitems) | Get Library Items | +| [refreshLibrary](#refreshlibrary) | Refresh Library | +| [getLatestLibraryItems](#getlatestlibraryitems) | Get Latest Library Items | +| [getCommonLibraryItems](#getcommonlibraryitems) | Get Common Library Items | +| [getMetadata](#getmetadata) | Get Items Metadata | +| [getMetadataChildren](#getmetadatachildren) | Get Items Children | +| [getOnDeck](#getondeck) | Get On Deck | + + +## Log + +| Method | Description| +| :-------- | :----------| +| [logMultiLine](#logmultiline) | Logging a multi-line message | +| [logLine](#logline) | Logging a single line message. | +| [enablePaperTrail](#enablepapertrail) | Enabling Papertrail | + + +## Playlists + +| Method | Description| +| :-------- | :----------| +| [createPlaylist](#createplaylist) | Create a Playlist | +| [getPlaylists](#getplaylists) | Get All Playlists | +| [getPlaylist](#getplaylist) | Retrieve Playlist | +| [deletePlaylist](#deleteplaylist) | Deletes a Playlist | +| [updatePlaylist](#updateplaylist) | Update a Playlist | +| [getPlaylistContents](#getplaylistcontents) | Retrieve Playlist Contents | +| [clearPlaylistContents](#clearplaylistcontents) | Delete Playlist Contents | +| [addPlaylistContents](#addplaylistcontents) | Adding to a Playlist | +| [uploadPlaylist](#uploadplaylist) | Upload Playlist | + + +## Security + +| Method | Description| +| :-------- | :----------| +| [getTransientToken](#gettransienttoken) | Get a Transient Token. | +| [getSourceConnectionInformation](#getsourceconnectioninformation) | Get Source Connection Information | + + +## Sessions + +| Method | Description| +| :-------- | :----------| +| [getSessions](#getsessions) | Get Active Sessions | +| [getSessionHistory](#getsessionhistory) | Get Session History | +| [getTranscodeSessions](#gettranscodesessions) | Get Transcode Sessions | +| [stopTranscodeSession](#stoptranscodesession) | Stop a Transcode Session | + + +## Updater + +| Method | Description| +| :-------- | :----------| +| [getUpdateStatus](#getupdatestatus) | Querying status of updates | +| [checkForUpdates](#checkforupdates) | Checking for updates | +| [applyUpdates](#applyupdates) | Apply Updates | + + +## Video + +| Method | Description| +| :-------- | :----------| +| [startUniversalTranscode](#startuniversaltranscode) | Start Universal Transcode | +| [getTimeline](#gettimeline) | Get the timeline for a media item | + + + + +## All Methods + + +### **getServerCapabilities** +Server Capabilities +- HTTP Method: GET +- Endpoint: / + + +**Return Type** + +GetServerCapabilitiesResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerCapabilities(); + console.log(result.data); +})(); + +``` + +### **getServerPreferences** +Get Server Preferences +- HTTP Method: GET +- Endpoint: /:/prefs + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerPreferences(); + console.log(result.data); +})(); + +``` + +### **getAvailableClients** +Get Available Clients +- HTTP Method: GET +- Endpoint: /clients + + +**Return Type** + +GetAvailableClientsResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getAvailableClients(); + console.log(result.data); +})(); + +``` + +### **getDevices** +Get Devices +- HTTP Method: GET +- Endpoint: /devices + + +**Return Type** + +GetDevicesResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getDevices(); + console.log(result.data); +})(); + +``` + +### **getServerIdentity** +Get Server Identity +- HTTP Method: GET +- Endpoint: /identity + + +**Return Type** + +GetServerIdentityResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerIdentity(); + console.log(result.data); +})(); + +``` + +### **getMyPlexAccount** +Get MyPlex Account +- HTTP Method: GET +- Endpoint: /myplex/account + + +**Return Type** + +GetMyPlexAccountResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getMyPlexAccount(); + console.log(result.data); +})(); + +``` + +### **getResizedPhoto** +Get a Resized Photo +- HTTP Method: GET +- Endpoint: /photo/:/transcode + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| width | number | The width for the resized photo | +| height | number | The height for the resized photo | +| opacity | number | The opacity for the resized photo | +| blur | number | The width for the resized photo | +| minSize | [MinSize](/src/models/README.md#minsize) | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. | +| upscale | [Upscale](/src/models/README.md#upscale) | allow images to be resized beyond native dimensions. | +| url | string | path to image within Plex | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getResizedPhoto( + 110, + 165, + 100, + 4000, + 1, + 1, + '/library/metadata/49564/thumb/1654258204', + ); + console.log(result.data); +})(); + +``` + +### **getServerList** +Get Server List +- HTTP Method: GET +- Endpoint: /servers + + +**Return Type** + +GetServerListResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.server.getServerList(); + console.log(result.data); +})(); + +``` + + +### **markPlayed** +Mark Media Played +- HTTP Method: GET +- Endpoint: /:/scrobble + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| key | number | The media key to mark as played | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.media.markPlayed(59398); + console.log(result.data); +})(); + +``` + +### **markUnplayed** +Mark Media Unplayed +- HTTP Method: GET +- Endpoint: /:/unscrobble + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| key | number | The media key to mark as Unplayed | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.media.markUnplayed(59398); + console.log(result.data); +})(); + +``` + +### **updatePlayProgress** +Update Media Play Progress +- HTTP Method: POST +- Endpoint: /:/progress + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| key | string | the media key | +| time | number | The time, in milliseconds, used to set the media playback progress. | +| state | string | The playback state of the media item. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.media.updatePlayProgress('key', -67598765.07465324, 'state'); + console.log(result.data); +})(); + +``` + + +### **getServerActivities** +Get Server Activities +- HTTP Method: GET +- Endpoint: /activities + + +**Return Type** + +GetServerActivitiesResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.activities.getServerActivities(); + console.log(result.data); +})(); + +``` + +### **cancelServerActivities** +Cancel Server Activities +- HTTP Method: DELETE +- Endpoint: /activities/{activityUUID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| activityUuid | string | The UUID of the activity to cancel. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.activities.cancelServerActivities('activityUUID'); + console.log(result.data); +})(); + +``` + + +### **startAllTasks** +Start all Butler tasks +- HTTP Method: POST +- Endpoint: /butler + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.startAllTasks(); + console.log(result.data); +})(); + +``` + +### **getButlerTasks** +Get Butler tasks +- HTTP Method: GET +- Endpoint: /butler + + +**Return Type** + +GetButlerTasksResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.getButlerTasks(); + console.log(result.data); +})(); + +``` + +### **stopAllTasks** +Stop all Butler tasks +- HTTP Method: DELETE +- Endpoint: /butler + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.stopAllTasks(); + console.log(result.data); +})(); + +``` + +### **startTask** +Start a single Butler task +- HTTP Method: POST +- Endpoint: /butler/{taskName} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| taskName | [TaskName](/src/models/README.md#taskname) | the name of the task to be started. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.startTask('RefreshLibraries'); + console.log(result.data); +})(); + +``` + +### **stopTask** +Stop a single Butler task +- HTTP Method: DELETE +- Endpoint: /butler/{taskName} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| taskName | [TaskName](/src/models/README.md#taskname) | The name of the task to be started. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.butler.stopTask('DeepMediaAnalysis'); + console.log(result.data); +})(); + +``` + + +### **getGlobalHubs** +Get Global Hubs +- HTTP Method: GET +- Endpoint: /hubs + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| count | number | The number of items to return with each hub. | +| onlyTransient | [OnlyTransient](/src/models/README.md#onlytransient) | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.hubs.getGlobalHubs({ count: 28368865.713739887, onlyTransient: 1 }); + console.log(result.data); +})(); + +``` + +### **getLibraryHubs** +Get library specific hubs +- HTTP Method: GET +- Endpoint: /hubs/sections/{sectionId} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| count | number | The number of items to return with each hub. | +| onlyTransient | [OnlyTransient](/src/models/README.md#onlytransient) | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.hubs.getLibraryHubs(18589771.015234828, { + count: -32594130.898548722, + onlyTransient: 1, + }); + console.log(result.data); +})(); + +``` + + +### **performSearch** +Perform a search +- HTTP Method: GET +- Endpoint: /hubs/search + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| query | string | The query term | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | number | The number of items to return per hub | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.search.performSearch('arnold', { + sectionId: 44691501.34992224, + limit: 5, + }); + console.log(result.data); +})(); + +``` + +### **performVoiceSearch** +Perform a voice search +- HTTP Method: GET +- Endpoint: /hubs/search/voice + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| query | string | The query term | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | number | The number of items to return per hub | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.search.performVoiceSearch('dead+poop', { + sectionId: 63927189.69130659, + limit: 5, + }); + console.log(result.data); +})(); + +``` + +### **getSearchResults** +Get Search Results +- HTTP Method: GET +- Endpoint: /search + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| query | string | The search query string to use | + + + +**Return Type** + +GetSearchResultsResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.search.getSearchResults('110'); + console.log(result.data); +})(); + +``` + + +### **getFileHash** +Get Hash Value +- HTTP Method: GET +- Endpoint: /library/hashes + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| url | string | This is the path to the local file, must be prefixed by `file://` | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| type | number | Item type | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getFileHash('file://C:Image.png&type=13', { + type: -40347992.189137824, + }); + console.log(result.data); +})(); + +``` + +### **getRecentlyAdded** +Get Recently Added +- HTTP Method: GET +- Endpoint: /library/recentlyAdded + + +**Return Type** + +GetRecentlyAddedResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getRecentlyAdded(); + console.log(result.data); +})(); + +``` + +### **getLibraries** +Get All Libraries +- HTTP Method: GET +- Endpoint: /library/sections + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLibraries(); + console.log(result.data); +})(); + +``` + +### **getLibrary** +Get Library Details +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| includeDetails | [IncludeDetails](/src/models/README.md#includedetails) | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
| + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLibrary(1000, { includeDetails: 42 }); + console.log(result.data); +})(); + +``` + +### **deleteLibrary** +Delete Library Section +- HTTP Method: DELETE +- Endpoint: /library/sections/{sectionId} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.deleteLibrary(1000); + console.log(result.data); +})(); + +``` + +### **getLibraryItems** +Get Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/all + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| type | number | item type | +| filter | string | the filter parameter | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLibraryItems(72139049.19684184, { + type: -38282492.854482666, + filter: 'filter', + }); + console.log(result.data); +})(); + +``` + +### **refreshLibrary** +Refresh Library +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/refresh + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to refresh | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.refreshLibrary(-48997883.13740698); + console.log(result.data); +})(); + +``` + +### **getLatestLibraryItems** +Get Latest Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/latest + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | +| type | number | item type | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| filter | string | the filter parameter | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getLatestLibraryItems(58797973.85610509, -91925704.44457702, { + filter: 'filter', + }); + console.log(result.data); +})(); + +``` + +### **getCommonLibraryItems** +Get Common Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/common + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sectionId | number | the Id of the library to query | +| type | number | item type | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| filter | string | the filter parameter | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getCommonLibraryItems(-1929766.2595285624, 10178756.232577473, { + filter: 'filter', + }); + console.log(result.data); +})(); + +``` + +### **getMetadata** +Get Items Metadata +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| ratingKey | number | the id of the library item to return the children of. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getMetadata(-93116760.96986176); + console.log(result.data); +})(); + +``` + +### **getMetadataChildren** +Get Items Children +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey}/children + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| ratingKey | number | the id of the library item to return the children of. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getMetadataChildren(74240120.76203653); + console.log(result.data); +})(); + +``` + +### **getOnDeck** +Get On Deck +- HTTP Method: GET +- Endpoint: /library/onDeck + + +**Return Type** + +GetOnDeckResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.library.getOnDeck(); + console.log(result.data); +})(); + +``` + + +### **logMultiLine** +Logging a multi-line message +- HTTP Method: POST +- Endpoint: /log + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.log.logMultiLine(); + console.log(result.data); +})(); + +``` + +### **logLine** +Logging a single line message. +- HTTP Method: GET +- Endpoint: /log + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| level | [Level](/src/models/README.md#level) | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
| +| message | string | The text of the message to write to the log. | +| source | string | a string indicating the source of the message. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.log.logLine(4, 'message', 'source'); + console.log(result.data); +})(); + +``` + +### **enablePaperTrail** +Enabling Papertrail +- HTTP Method: GET +- Endpoint: /log/networked + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.log.enablePaperTrail(); + console.log(result.data); +})(); + +``` + + +### **createPlaylist** +Create a Playlist +- HTTP Method: POST +- Endpoint: /playlists + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| title | string | name of the playlist | +| type | [Type](/src/models/README.md#type) | type of playlist to create | +| smart | [Smart](/src/models/README.md#smart) | whether the playlist is smart or not | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| uri | string | the content URI for the playlist | +| playQueueId | number | the play queue to copy to a playlist | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.createPlaylist('title', 'video', 1, { + uri: 'uri', + playQueueID: 8848570.752491549, + }); + console.log(result.data); +})(); + +``` + +### **getPlaylists** +Get All Playlists +- HTTP Method: GET +- Endpoint: /playlists/all + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistType | [PlaylistType](/src/models/README.md#playlisttype) | limit to a type of playlist. | +| smart | [Smart](/src/models/README.md#smart) | type of playlists to return (default is all). | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.getPlaylists({ playlistType: 'photo', smart: 1 }); + console.log(result.data); +})(); + +``` + +### **getPlaylist** +Retrieve Playlist +- HTTP Method: GET +- Endpoint: /playlists/{playlistID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.getPlaylist(-26157175.250883877); + console.log(result.data); +})(); + +``` + +### **deletePlaylist** +Deletes a Playlist +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.deletePlaylist(98575271.53317443); + console.log(result.data); +})(); + +``` + +### **updatePlaylist** +Update a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.updatePlaylist(-63882285.47098422); + console.log(result.data); +})(); + +``` + +### **getPlaylistContents** +Retrieve Playlist Contents +- HTTP Method: GET +- Endpoint: /playlists/{playlistID}/items + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | +| type | number | the metadata type of the item to return | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.getPlaylistContents(39702400.82414672, 90162914.95216954); + console.log(result.data); +})(); + +``` + +### **clearPlaylistContents** +Delete Playlist Contents +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID}/items + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.clearPlaylistContents(12387577.811442614); + console.log(result.data); +})(); + +``` + +### **addPlaylistContents** +Adding to a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID}/items + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| playlistId | number | the ID of the playlist | +| uri | string | the content URI for the playlist | +| playQueueId | number | the play queue to add to a playlist | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.addPlaylistContents(68171801.32332289, 'library://..', 123); + console.log(result.data); +})(); + +``` + +### **uploadPlaylist** +Upload Playlist +- HTTP Method: POST +- Endpoint: /playlists/upload + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| path | string | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
| +| force | [Force](/src/models/README.md#force) | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
| + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.playlists.uploadPlaylist('/home/barkley/playlist.m3u', 1); + console.log(result.data); +})(); + +``` + + +### **getTransientToken** +Get a Transient Token. +- HTTP Method: GET +- Endpoint: /security/token + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| type | [SecurityType](/src/models/README.md#securitytype) | `delegation` - This is the only supported `type` parameter. | +| scope | [Scope](/src/models/README.md#scope) | `all` - This is the only supported `scope` parameter. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.security.getTransientToken('delegation', 'all'); + console.log(result.data); +})(); + +``` + +### **getSourceConnectionInformation** +Get Source Connection Information +- HTTP Method: GET +- Endpoint: /security/resources + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| source | string | The source identifier with an included prefix. | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.security.getSourceConnectionInformation( + 'provider://provider-identifier', + ); + console.log(result.data); +})(); + +``` + + +### **getSessions** +Get Active Sessions +- HTTP Method: GET +- Endpoint: /status/sessions + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.getSessions(); + console.log(result.data); +})(); + +``` + +### **getSessionHistory** +Get Session History +- HTTP Method: GET +- Endpoint: /status/sessions/history/all + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.getSessionHistory(); + console.log(result.data); +})(); + +``` + +### **getTranscodeSessions** +Get Transcode Sessions +- HTTP Method: GET +- Endpoint: /transcode/sessions + + +**Return Type** + +GetTranscodeSessionsResponse + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.getTranscodeSessions(); + console.log(result.data); +})(); + +``` + +### **stopTranscodeSession** +Stop a Transcode Session +- HTTP Method: DELETE +- Endpoint: /transcode/sessions/{sessionKey} + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| sessionKey | string | the Key of the transcode session to stop | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.sessions.stopTranscodeSession('zz7llzqlx8w9vnrsbnwhbmep'); + console.log(result.data); +})(); + +``` + + +### **getUpdateStatus** +Querying status of updates +- HTTP Method: GET +- Endpoint: /updater/status + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.updater.getUpdateStatus(); + console.log(result.data); +})(); + +``` + +### **checkForUpdates** +Checking for updates +- HTTP Method: PUT +- Endpoint: /updater/check + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| download | [Download](/src/models/README.md#download) | Indicate that you want to start download any updates found. | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.updater.checkForUpdates({ download: 1 }); + console.log(result.data); +})(); + +``` + +### **applyUpdates** +Apply Updates +- HTTP Method: PUT +- Endpoint: /updater/apply + + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| tonight | [Tonight](/src/models/README.md#tonight) | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install | +| skip | [Skip](/src/models/README.md#skip) | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.updater.applyUpdates({ tonight: 1, skip: 1 }); + console.log(result.data); +})(); + +``` + + +### **startUniversalTranscode** +Start Universal Transcode +- HTTP Method: GET +- Endpoint: /video/:/transcode/universal/start.mpd + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| hasMde | number | Whether the media item has MDE | +| path | string | The path to the media item to transcode | +| mediaIndex | number | The index of the media item to transcode | +| partIndex | number | The index of the part to transcode | +| protocol | string | The protocol to use for the transcode session | + +**Optional Parameters** + +Optional parameters are passed as part of the last parameter to the method. Ex. {optionalParam1 : 'value1', optionalParam2: 'value2'} + +| Name | Type| Description | +| :-------- | :----------| :----------| +| fastSeek | number | Whether to use fast seek or not | +| directPlay | number | Whether to use direct play or not | +| directStream | number | Whether to use direct stream or not | +| subtitleSize | number | The size of the subtitles | +| subtites | string | The subtitles | +| audioBoost | number | The audio boost | +| location | string | The location of the transcode session | +| mediaBufferSize | number | The size of the media buffer | +| session | string | The session ID | +| addDebugOverlay | number | Whether to add a debug overlay or not | +| autoAdjustQuality | number | Whether to auto adjust quality or not | + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.video.startUniversalTranscode( + -72522752.76989551, + 'path', + 82041936.41398731, + -84753516.90588528, + 'protocol', + { + fastSeek: -30142578.11776738, + directPlay: 9919137.354697362, + directStream: -19025857.514729157, + subtitleSize: -84468293.84527272, + subtites: 'subtites', + audioBoost: -68940176.38612957, + location: 'location', + mediaBufferSize: 30606631.50083433, + session: 'session', + addDebugOverlay: -70696815.34237362, + autoAdjustQuality: -83810577.71928595, + }, + ); + console.log(result.data); +})(); + +``` + +### **getTimeline** +Get the timeline for a media item +- HTTP Method: GET +- Endpoint: /:/timeline + +**Required Parameters** + +| Name | Type| Description | +| :-------- | :----------| :----------| +| ratingKey | number | The rating key of the media item | +| key | string | The key of the media item to get the timeline for | +| state | [State](/src/models/README.md#state) | The state of the media item | +| hasMde | number | Whether the media item has MDE | +| time | number | The time of the media item | +| duration | number | The duration of the media item | +| context | string | The context of the media item | +| playQueueItemId | number | The play queue item ID of the media item | +| playBackTime | number | The playback time of the media item | +| row | number | The row of the media item | + + + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Typescript +import { PlexSDK } from './src'; + +const sdk = new PlexSDK({ apiKey: process.env.PLEXSDK_API_KEY_TOKEN }); + +(async () => { + const result = await sdk.video.getTimeline( + 64550873.97298089, + 'key', + 'playing', + -30122195.47065121, + 85753219.1612091, + -36475708.52265885, + 'context', + 26769044.884643376, + 36998590.48639223, + -3788221.7909231335, + ); + console.log(result.data); +})(); + +``` + + + + diff --git a/src/services/activities/Activities.ts b/src/services/activities/Activities.ts new file mode 100644 index 00000000..a41b2d7a --- /dev/null +++ b/src/services/activities/Activities.ts @@ -0,0 +1,67 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { GetServerActivitiesResponse } from './models/GetServerActivitiesResponse'; + +import { serializePath } from '../../http/QuerySerializer'; + +export class ActivitiesService extends BaseService { + /** + * @summary Get Server Activities + * @description Get Server Activities + + * @returns {Promise>} - The promise with the result + */ + async getServerActivities(): Promise> { + const urlEndpoint = '/activities'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Cancel Server Activities + * @description Cancel Server Activities + + * @param activityUUID The UUID of the activity to cancel. + * @returns {Promise} - The promise with the result + */ + async cancelServerActivities(activityUuid: string): Promise { + if (activityUuid === undefined) { + throw new Error( + 'The following parameter is required: activityUuid, cannot be empty or blank', + ); + } + let urlEndpoint = '/activities/{activityUUID}'; + urlEndpoint = urlEndpoint.replace( + '{activityUUID}', + encodeURIComponent(serializePath('simple', false, activityUuid, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/activities/index.ts b/src/services/activities/index.ts new file mode 100644 index 00000000..d67c1001 --- /dev/null +++ b/src/services/activities/index.ts @@ -0,0 +1 @@ +export type { GetServerActivitiesResponse } from './models/GetServerActivitiesResponse'; diff --git a/src/services/activities/models/GetServerActivitiesResponse.ts b/src/services/activities/models/GetServerActivitiesResponse.ts new file mode 100644 index 00000000..d86e6f6e --- /dev/null +++ b/src/services/activities/models/GetServerActivitiesResponse.ts @@ -0,0 +1,19 @@ +export interface GetServerActivitiesResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + Activity?: { + uuid?: string; + cancellable?: boolean; + userID?: number; + title?: string; + subtitle?: string; + progress?: number; + Context?: Context; + type_?: string; + }[]; +} +interface Context { + librarySectionID?: string; +} diff --git a/src/services/butler/Butler.ts b/src/services/butler/Butler.ts new file mode 100644 index 00000000..a7179838 --- /dev/null +++ b/src/services/butler/Butler.ts @@ -0,0 +1,158 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { GetButlerTasksResponse } from './models/GetButlerTasksResponse'; +import { TaskName } from './models/TaskName'; + +import { serializePath } from '../../http/QuerySerializer'; + +export class ButlerService extends BaseService { + /** + * @summary Get Butler tasks + * @description Returns a list of butler tasks + + * @returns {Promise>} - The promise with the result + */ + async getButlerTasks(): Promise> { + const urlEndpoint = '/butler'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Start all Butler tasks + * @description This endpoint will attempt to start all Butler tasks that are enabled in the settings. Butler tasks normally run automatically during a time window configured on the server's Settings page but can be manually started using this endpoint. Tasks will run with the following criteria: +1. Any tasks not scheduled to run on the current day will be skipped. +2. If a task is configured to run at a random time during the configured window and we are outside that window, the task will start immediately. +3. If a task is configured to run at a random time during the configured window and we are within that window, the task will be scheduled at a random time within the window. +4. If we are outside the configured window, the task will start immediately. + + + * @returns {Promise} - The promise with the result + */ + async startAllTasks(): Promise { + const urlEndpoint = '/butler'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.post( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Stop all Butler tasks + * @description This endpoint will stop all currently running tasks and remove any scheduled tasks from the queue. + + + * @returns {Promise} - The promise with the result + */ + async stopAllTasks(): Promise { + const urlEndpoint = '/butler'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Start a single Butler task + * @description This endpoint will attempt to start a single Butler task that is enabled in the settings. Butler tasks normally run automatically during a time window configured on the server's Settings page but can be manually started using this endpoint. Tasks will run with the following criteria: +1. Any tasks not scheduled to run on the current day will be skipped. +2. If a task is configured to run at a random time during the configured window and we are outside that window, the task will start immediately. +3. If a task is configured to run at a random time during the configured window and we are within that window, the task will be scheduled at a random time within the window. +4. If we are outside the configured window, the task will start immediately. + + + * @param taskName the name of the task to be started. + * @returns {Promise} - The promise with the result + */ + async startTask(taskName: TaskName): Promise { + if (taskName === undefined) { + throw new Error('The following parameter is required: taskName, cannot be empty or blank'); + } + let urlEndpoint = '/butler/{taskName}'; + urlEndpoint = urlEndpoint.replace( + '{taskName}', + encodeURIComponent(serializePath('simple', false, taskName, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.post( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Stop a single Butler task + * @description This endpoint will stop a currently running task by name, or remove it from the list of scheduled tasks if it exists. See the section above for a list of task names for this endpoint. + + + * @param taskName The name of the task to be started. + * @returns {Promise} - The promise with the result + */ + async stopTask(taskName: TaskName): Promise { + if (taskName === undefined) { + throw new Error('The following parameter is required: taskName, cannot be empty or blank'); + } + let urlEndpoint = '/butler/{taskName}'; + urlEndpoint = urlEndpoint.replace( + '{taskName}', + encodeURIComponent(serializePath('simple', false, taskName, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/butler/index.ts b/src/services/butler/index.ts new file mode 100644 index 00000000..b9f1a580 --- /dev/null +++ b/src/services/butler/index.ts @@ -0,0 +1,2 @@ +export type { GetButlerTasksResponse } from './models/GetButlerTasksResponse'; +export type { TaskName } from './models/TaskName'; diff --git a/src/services/butler/models/GetButlerTasksResponse.ts b/src/services/butler/models/GetButlerTasksResponse.ts new file mode 100644 index 00000000..dcc7fddd --- /dev/null +++ b/src/services/butler/models/GetButlerTasksResponse.ts @@ -0,0 +1,13 @@ +export interface GetButlerTasksResponse { + ButlerTasks?: ButlerTasks; +} +interface ButlerTasks { + ButlerTask?: { + name?: string; + interval?: number; + scheduleRandomized?: boolean; + enabled?: boolean; + title?: string; + description?: string; + }[]; +} diff --git a/src/services/butler/models/TaskName.ts b/src/services/butler/models/TaskName.ts new file mode 100644 index 00000000..5d3a65b8 --- /dev/null +++ b/src/services/butler/models/TaskName.ts @@ -0,0 +1,15 @@ +export type TaskName = + | 'BackupDatabase' + | 'BuildGracenoteCollections' + | 'CheckForUpdates' + | 'CleanOldBundles' + | 'CleanOldCacheFiles' + | 'DeepMediaAnalysis' + | 'GenerateAutoTags' + | 'GenerateChapterThumbs' + | 'GenerateMediaIndexFiles' + | 'OptimizeDatabase' + | 'RefreshLibraries' + | 'RefreshLocalMedia' + | 'RefreshPeriodicMetadata' + | 'UpgradeMediaAnalysis'; diff --git a/src/services/hubs/Hubs.ts b/src/services/hubs/Hubs.ts new file mode 100644 index 00000000..8fdd10a3 --- /dev/null +++ b/src/services/hubs/Hubs.ts @@ -0,0 +1,96 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { OnlyTransient } from './models/OnlyTransient'; + +import { serializeQuery, serializePath } from '../../http/QuerySerializer'; + +export class HubsService extends BaseService { + /** + * @summary Get Global Hubs + * @description Get Global Hubs filtered by the parameters provided. + + * @param optionalParams - Optional parameters + * @param optionalParams.count - The number of items to return with each hub. + * @param optionalParams.onlyTransient - Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). + * @returns {Promise} - The promise with the result + */ + async getGlobalHubs( + optionalParams: { count?: number; onlyTransient?: OnlyTransient } = {}, + ): Promise { + const { count, onlyTransient } = optionalParams; + + const queryParams: string[] = []; + if (count) { + queryParams.push(serializeQuery('form', true, 'count', count)); + } + if (onlyTransient) { + queryParams.push(serializeQuery('form', true, 'onlyTransient', onlyTransient)); + } + const urlEndpoint = '/hubs'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get library specific hubs + * @description This endpoint will return a list of library specific hubs + + + * @param sectionId the Id of the library to query + * @param optionalParams - Optional parameters + * @param optionalParams.count - The number of items to return with each hub. + * @param optionalParams.onlyTransient - Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). + * @returns {Promise} - The promise with the result + */ + async getLibraryHubs( + sectionId: number, + optionalParams: { count?: number; onlyTransient?: OnlyTransient } = {}, + ): Promise { + const { count, onlyTransient } = optionalParams; + if (sectionId === undefined) { + throw new Error('The following parameter is required: sectionId, cannot be empty or blank'); + } + const queryParams: string[] = []; + let urlEndpoint = '/hubs/sections/{sectionId}'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + if (count) { + queryParams.push(serializeQuery('form', true, 'count', count)); + } + if (onlyTransient) { + queryParams.push(serializeQuery('form', true, 'onlyTransient', onlyTransient)); + } + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/hubs/index.ts b/src/services/hubs/index.ts new file mode 100644 index 00000000..77fff8d2 --- /dev/null +++ b/src/services/hubs/index.ts @@ -0,0 +1 @@ +export type { OnlyTransient } from './models/OnlyTransient'; diff --git a/src/services/hubs/models/OnlyTransient.ts b/src/services/hubs/models/OnlyTransient.ts new file mode 100644 index 00000000..ebc724f6 --- /dev/null +++ b/src/services/hubs/models/OnlyTransient.ts @@ -0,0 +1 @@ +export type OnlyTransient = 0 | 1; diff --git a/src/services/library/Library.ts b/src/services/library/Library.ts new file mode 100644 index 00000000..b55e42ec --- /dev/null +++ b/src/services/library/Library.ts @@ -0,0 +1,475 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { GetRecentlyAddedResponse } from './models/GetRecentlyAddedResponse'; +import { IncludeDetails } from './models/IncludeDetails'; +import { GetOnDeckResponse } from './models/GetOnDeckResponse'; + +import { serializeQuery, serializePath } from '../../http/QuerySerializer'; + +export class LibraryService extends BaseService { + /** + * @summary Get Hash Value + * @description This resource returns hash values for local files + + * @param url This is the path to the local file, must be prefixed by `file://` + * @param optionalParams - Optional parameters + * @param optionalParams.type_ - Item type + * @returns {Promise} - The promise with the result + */ + async getFileHash(url: string, optionalParams: { type?: number } = {}): Promise { + const { type } = optionalParams; + if (url === undefined) { + throw new Error('The following parameter is required: url, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (url) { + queryParams.push(serializeQuery('form', true, 'url', url)); + } + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + const urlEndpoint = '/library/hashes'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Recently Added + * @description This endpoint will return the recently added content. + + + * @returns {Promise>} - The promise with the result + */ + async getRecentlyAdded(): Promise> { + const urlEndpoint = '/library/recentlyAdded'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get All Libraries + * @description A library section (commonly referred to as just a library) is a collection of media. +Libraries are typed, and depending on their type provide either a flat or a hierarchical view of the media. +For example, a music library has an artist > albums > tracks structure, whereas a movie library is flat. + +Libraries have features beyond just being a collection of media; for starters, they include information about supported types, filters and sorts. +This allows a client to provide a rich interface around the media (e.g. allow sorting movies by release year). + + + * @returns {Promise} - The promise with the result + */ + async getLibraries(): Promise { + const urlEndpoint = '/library/sections'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Library Details + * @description Returns details for the library. This can be thought of as an interstitial endpoint because it contains information about the library, rather than content itself. These details are: + +- A list of `Directory` objects: These used to be used by clients to build a menuing system. There are four flavors of directory found here: + - Primary: (e.g. all, On Deck) These are still used in some clients to provide "shortcuts" to subsets of media. However, with the exception of On Deck, all of them can be created by media queries, and the desire is to allow these to be customized by users. + - Secondary: These are marked with `secondary="1"` and were used by old clients to provide nested menus allowing for primative (but structured) navigation. + - Special: There is a By Folder entry which allows browsing the media by the underlying filesystem structure, and there's a completely obsolete entry marked `search="1"` which used to be used to allow clients to build search dialogs on the fly. +- A list of `Type` objects: These represent the types of things found in this library, and for each one, a list of `Filter` and `Sort` objects. These can be used to build rich controls around a grid of media to allow filtering and organizing. Note that these filters and sorts are optional, and without them, the client won't render any filtering controls. The `Type` object contains: + - `key`: This provides the root endpoint returning the actual media list for the type. + - `type`: This is the metadata type for the type (if a standard Plex type). + - `title`: The title for for the content of this type (e.g. "Movies"). +- Each `Filter` object contains a description of the filter. Note that it is not an exhaustive list of the full media query language, but an inportant subset useful for top-level API. + - `filter`: This represents the filter name used for the filter, which can be used to construct complex media queries with. + - `filterType`: This is either `string`, `integer`, or `boolean`, and describes the type of values used for the filter. + - `key`: This provides the endpoint where the possible range of values for the filter can be retrieved (e.g. for a "Genre" filter, it returns a list of all the genres in the library). This will include a `type` argument that matches the metadata type of the Type element. + - `title`: The title for the filter. +- Each `Sort` object contains a description of the sort field. + - `defaultDirection`: Can be either `asc` or `desc`, and specifies the default direction for the sort field (e.g. titles default to alphabetically ascending). + - `descKey` and `key`: Contains the parameters passed to the `sort=...` media query for each direction of the sort. + - `title`: The title of the field. + + + * @param sectionId the Id of the library to query + * @param optionalParams - Optional parameters + * @param optionalParams.includeDetails - Whether or not to include details for a section (types, filters, and sorts). +Only exists for backwards compatibility, media providers other than the server libraries have it on always. + + * @returns {Promise} - The promise with the result + */ + async getLibrary( + sectionId: number, + optionalParams: { includeDetails?: IncludeDetails } = {}, + ): Promise { + const { includeDetails } = optionalParams; + if (sectionId === undefined) { + throw new Error('The following parameter is required: sectionId, cannot be empty or blank'); + } + const queryParams: string[] = []; + let urlEndpoint = '/library/sections/{sectionId}'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + if (includeDetails) { + queryParams.push(serializeQuery('form', true, 'includeDetails', includeDetails)); + } + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Delete Library Section + * @description Delate a library using a specific section + + * @param sectionId the Id of the library to query + * @returns {Promise} - The promise with the result + */ + async deleteLibrary(sectionId: number): Promise { + if (sectionId === undefined) { + throw new Error('The following parameter is required: sectionId, cannot be empty or blank'); + } + let urlEndpoint = '/library/sections/{sectionId}'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Library Items + * @description This endpoint will return a list of library items filtered by the filter and type provided + + + * @param sectionId the Id of the library to query + * @param optionalParams - Optional parameters + * @param optionalParams.type_ - item type + * @param optionalParams.filter - the filter parameter + * @returns {Promise} - The promise with the result + */ + async getLibraryItems( + sectionId: number, + optionalParams: { type?: number; filter?: string } = {}, + ): Promise { + const { type, filter } = optionalParams; + if (sectionId === undefined) { + throw new Error('The following parameter is required: sectionId, cannot be empty or blank'); + } + const queryParams: string[] = []; + let urlEndpoint = '/library/sections/{sectionId}/all'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + if (filter) { + queryParams.push(serializeQuery('form', true, 'filter', filter)); + } + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Refresh Library + * @description This endpoint Refreshes the library. + + + * @param sectionId the Id of the library to refresh + * @returns {Promise} - The promise with the result + */ + async refreshLibrary(sectionId: number): Promise { + if (sectionId === undefined) { + throw new Error('The following parameter is required: sectionId, cannot be empty or blank'); + } + let urlEndpoint = '/library/sections/{sectionId}/refresh'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Latest Library Items + * @description This endpoint will return a list of the latest library items filtered by the filter and type provided + + + * @param sectionId the Id of the library to query + * @param type_ item type + * @param optionalParams - Optional parameters + * @param optionalParams.filter - the filter parameter + * @returns {Promise} - The promise with the result + */ + async getLatestLibraryItems( + sectionId: number, + type: number, + optionalParams: { filter?: string } = {}, + ): Promise { + const { filter } = optionalParams; + if (sectionId === undefined || type === undefined) { + throw new Error( + 'The following are required parameters: sectionId,type, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + let urlEndpoint = '/library/sections/{sectionId}/latest'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + if (filter) { + queryParams.push(serializeQuery('form', true, 'filter', filter)); + } + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Common Library Items + * @description Represents a "Common" item. It contains only the common attributes of the items selected by the provided filter + + + * @param sectionId the Id of the library to query + * @param type_ item type + * @param optionalParams - Optional parameters + * @param optionalParams.filter - the filter parameter + * @returns {Promise} - The promise with the result + */ + async getCommonLibraryItems( + sectionId: number, + type: number, + optionalParams: { filter?: string } = {}, + ): Promise { + const { filter } = optionalParams; + if (sectionId === undefined || type === undefined) { + throw new Error( + 'The following are required parameters: sectionId,type, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + let urlEndpoint = '/library/sections/{sectionId}/common'; + urlEndpoint = urlEndpoint.replace( + '{sectionId}', + encodeURIComponent(serializePath('simple', false, sectionId, undefined)), + ); + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + if (filter) { + queryParams.push(serializeQuery('form', true, 'filter', filter)); + } + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Items Metadata + * @description This endpoint will return the metadata of a library item specified with the ratingKey. + + + * @param ratingKey the id of the library item to return the children of. + * @returns {Promise} - The promise with the result + */ + async getMetadata(ratingKey: number): Promise { + if (ratingKey === undefined) { + throw new Error('The following parameter is required: ratingKey, cannot be empty or blank'); + } + let urlEndpoint = '/library/metadata/{ratingKey}'; + urlEndpoint = urlEndpoint.replace( + '{ratingKey}', + encodeURIComponent(serializePath('simple', false, ratingKey, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Items Children + * @description This endpoint will return the children of of a library item specified with the ratingKey. + + + * @param ratingKey the id of the library item to return the children of. + * @returns {Promise} - The promise with the result + */ + async getMetadataChildren(ratingKey: number): Promise { + if (ratingKey === undefined) { + throw new Error('The following parameter is required: ratingKey, cannot be empty or blank'); + } + let urlEndpoint = '/library/metadata/{ratingKey}/children'; + urlEndpoint = urlEndpoint.replace( + '{ratingKey}', + encodeURIComponent(serializePath('simple', false, ratingKey, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get On Deck + * @description This endpoint will return the on deck content. + + + * @returns {Promise>} - The promise with the result + */ + async getOnDeck(): Promise> { + const urlEndpoint = '/library/onDeck'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/library/index.ts b/src/services/library/index.ts new file mode 100644 index 00000000..da0110c3 --- /dev/null +++ b/src/services/library/index.ts @@ -0,0 +1,3 @@ +export type { GetOnDeckResponse } from './models/GetOnDeckResponse'; +export type { GetRecentlyAddedResponse } from './models/GetRecentlyAddedResponse'; +export type { IncludeDetails } from './models/IncludeDetails'; diff --git a/src/services/library/models/GetOnDeckResponse.ts b/src/services/library/models/GetOnDeckResponse.ts new file mode 100644 index 00000000..33098097 --- /dev/null +++ b/src/services/library/models/GetOnDeckResponse.ts @@ -0,0 +1,101 @@ +export interface GetOnDeckResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + allowSync?: boolean; + identifier?: string; + mediaTagPrefix?: string; + mediaTagVersion?: number; + mixedParents?: boolean; + Metadata?: { + allowSync?: boolean; + librarySectionID?: number; + librarySectionTitle?: string; + librarySectionUUID?: string; + ratingKey?: number; + key?: string; + parentRatingKey?: number; + grandparentRatingKey?: number; + guid?: string; + parentGuid?: string; + grandparentGuid?: string; + title?: string; + grandparentKey?: string; + parentKey?: string; + librarySectionKey?: string; + grandparentTitle?: string; + parentTitle?: string; + contentRating?: string; + summary?: string; + index?: number; + parentIndex?: number; + lastViewedAt?: number; + year?: number; + thumb?: string; + art?: string; + parentThumb?: string; + grandparentThumb?: string; + grandparentArt?: string; + grandparentTheme?: string; + duration?: number; + originallyAvailableAt?: string; + addedAt?: number; + updatedAt?: number; + Media?: { + id?: number; + duration?: number; + bitrate?: number; + width?: number; + height?: number; + aspectRatio?: number; + audioChannels?: number; + audioCodec?: string; + videoCodec?: string; + videoResolution?: string; + container?: string; + videoFrameRate?: string; + audioProfile?: string; + videoProfile?: string; + Part?: { + id?: number; + key?: string; + duration?: number; + file?: string; + size?: number; + audioProfile?: string; + container?: string; + videoProfile?: string; + Stream?: { + id?: number; + streamType?: number; + codec?: string; + index?: number; + bitrate?: number; + language?: string; + languageTag?: string; + languageCode?: string; + bitDepth?: number; + chromaLocation?: string; + chromaSubsampling?: string; + codedHeight?: number; + codedWidth?: number; + colorRange?: string; + frameRate?: number; + height?: number; + level?: number; + profile?: string; + refFrames?: number; + width?: number; + displayTitle?: string; + extendedDisplayTitle?: string; + default_?: boolean; + }[]; + }[]; + }[]; + Guid?: { + id?: string; + }[]; + type_?: string; + }[]; +} diff --git a/src/services/library/models/GetRecentlyAddedResponse.ts b/src/services/library/models/GetRecentlyAddedResponse.ts new file mode 100644 index 00000000..e4c38378 --- /dev/null +++ b/src/services/library/models/GetRecentlyAddedResponse.ts @@ -0,0 +1,83 @@ +export interface GetRecentlyAddedResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + allowSync?: boolean; + identifier?: string; + mediaTagPrefix?: string; + mediaTagVersion?: number; + mixedParents?: boolean; + Metadata?: { + allowSync?: boolean; + librarySectionID?: number; + librarySectionTitle?: string; + librarySectionUUID?: string; + ratingKey?: number; + key?: string; + guid?: string; + studio?: string; + title?: string; + contentRating?: string; + summary?: string; + rating?: number; + audienceRating?: number; + year?: number; + tagline?: string; + thumb?: string; + art?: string; + duration?: number; + originallyAvailableAt?: string; + addedAt?: number; + updatedAt?: number; + audienceRatingImage?: string; + chapterSource?: string; + primaryExtraKey?: string; + ratingImage?: string; + Media?: { + id?: number; + duration?: number; + bitrate?: number; + width?: number; + height?: number; + aspectRatio?: number; + audioChannels?: number; + audioCodec?: string; + videoCodec?: string; + videoResolution?: number; + container?: string; + videoFrameRate?: string; + optimizedForStreaming?: number; + has64bitOffsets?: boolean; + videoProfile?: string; + Part?: { + id?: number; + key?: string; + duration?: number; + file?: string; + size?: number; + container?: string; + has64bitOffsets?: boolean; + hasThumbnail?: number; + optimizedForStreaming?: boolean; + videoProfile?: string; + }[]; + }[]; + Genre?: { + tag?: string; + }[]; + Director?: { + tag?: string; + }[]; + Writer?: { + tag?: string; + }[]; + Country?: { + tag?: string; + }[]; + Role?: { + tag?: string; + }[]; + type_?: string; + }[]; +} diff --git a/src/services/library/models/IncludeDetails.ts b/src/services/library/models/IncludeDetails.ts new file mode 100644 index 00000000..51045cf7 --- /dev/null +++ b/src/services/library/models/IncludeDetails.ts @@ -0,0 +1 @@ +export type IncludeDetails = 0 | 1; diff --git a/src/services/log/Log.ts b/src/services/log/Log.ts new file mode 100644 index 00000000..d08c267f --- /dev/null +++ b/src/services/log/Log.ts @@ -0,0 +1,108 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { Level } from './models/Level'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class LogService extends BaseService { + /** + * @summary Logging a single line message. + * @description This endpoint will write a single-line log message, including a level and source to the main Plex Media Server log. + + + * @param level An integer log level to write to the PMS log with. +0: Error +1: Warning +2: Info +3: Debug +4: Verbose + + * @param message The text of the message to write to the log. + * @param source a string indicating the source of the message. + * @returns {Promise} - The promise with the result + */ + async logLine(level: Level, message: string, source: string): Promise { + if (level === undefined || message === undefined || source === undefined) { + throw new Error( + 'The following are required parameters: level,message,source, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (level) { + queryParams.push(serializeQuery('form', true, 'level', level)); + } + if (message) { + queryParams.push(serializeQuery('form', true, 'message', message)); + } + if (source) { + queryParams.push(serializeQuery('form', true, 'source', source)); + } + const urlEndpoint = '/log'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Logging a multi-line message + * @description This endpoint will write multiple lines to the main Plex Media Server log in a single request. It takes a set of query strings as would normally sent to the above GET endpoint as a linefeed-separated block of POST data. The parameters for each query string match as above. + + + * @returns {Promise} - The promise with the result + */ + async logMultiLine(): Promise { + const urlEndpoint = '/log'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.post( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Enabling Papertrail + * @description This endpoint will enable all Plex Media Serverlogs to be sent to the Papertrail networked logging site for a period of time. + + + * @returns {Promise} - The promise with the result + */ + async enablePaperTrail(): Promise { + const urlEndpoint = '/log/networked'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/log/index.ts b/src/services/log/index.ts new file mode 100644 index 00000000..3db5021c --- /dev/null +++ b/src/services/log/index.ts @@ -0,0 +1 @@ +export type { Level } from './models/Level'; diff --git a/src/services/log/models/Level.ts b/src/services/log/models/Level.ts new file mode 100644 index 00000000..3cedb749 --- /dev/null +++ b/src/services/log/models/Level.ts @@ -0,0 +1 @@ +export type Level = 0 | 1 | 2 | 3 | 4; diff --git a/src/services/media/Media.ts b/src/services/media/Media.ts new file mode 100644 index 00000000..7e9e5dd5 --- /dev/null +++ b/src/services/media/Media.ts @@ -0,0 +1,114 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class MediaService extends BaseService { + /** + * @summary Mark Media Played + * @description This will mark the provided media key as Played. + + * @param key The media key to mark as played + * @returns {Promise} - The promise with the result + */ + async markPlayed(key: number): Promise { + if (key === undefined) { + throw new Error('The following parameter is required: key, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (key) { + queryParams.push(serializeQuery('form', true, 'key', key)); + } + const urlEndpoint = '/:/scrobble'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Mark Media Unplayed + * @description This will mark the provided media key as Unplayed. + + * @param key The media key to mark as Unplayed + * @returns {Promise} - The promise with the result + */ + async markUnplayed(key: number): Promise { + if (key === undefined) { + throw new Error('The following parameter is required: key, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (key) { + queryParams.push(serializeQuery('form', true, 'key', key)); + } + const urlEndpoint = '/:/unscrobble'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Update Media Play Progress + * @description This API command can be used to update the play progress of a media item. + + + * @param key the media key + * @param time The time, in milliseconds, used to set the media playback progress. + * @param state The playback state of the media item. + * @returns {Promise} - The promise with the result + */ + async updatePlayProgress(key: string, time: number, state: string): Promise { + if (key === undefined || time === undefined || state === undefined) { + throw new Error( + 'The following are required parameters: key,time,state, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (key) { + queryParams.push(serializeQuery('form', true, 'key', key)); + } + if (time) { + queryParams.push(serializeQuery('form', true, 'time', time)); + } + if (state) { + queryParams.push(serializeQuery('form', true, 'state', state)); + } + const urlEndpoint = '/:/progress'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.post( + finalUrl, + { key, time, state }, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/playlists/Playlists.ts b/src/services/playlists/Playlists.ts new file mode 100644 index 00000000..6d47689a --- /dev/null +++ b/src/services/playlists/Playlists.ts @@ -0,0 +1,380 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { Type } from './models/Type'; +import { Smart } from './models/Smart'; +import { PlaylistType } from './models/PlaylistType'; +import { Force } from './models/Force'; + +import { serializeQuery, serializePath } from '../../http/QuerySerializer'; + +export class PlaylistsService extends BaseService { + /** + * @summary Create a Playlist + * @description Create a new playlist. By default the playlist is blank. To create a playlist along with a first item, pass: +- `uri` - The content URI for what we're playing (e.g. `library://...`). +- `playQueueID` - To create a playlist from an existing play queue. + + + * @param title name of the playlist + * @param type_ type of playlist to create + * @param smart whether the playlist is smart or not + * @param optionalParams - Optional parameters + * @param optionalParams.uri - the content URI for the playlist + * @param optionalParams.playQueueID - the play queue to copy to a playlist + * @returns {Promise} - The promise with the result + */ + async createPlaylist( + title: string, + type: Type, + smart: Smart, + optionalParams: { uri?: string; playQueueId?: number } = {}, + ): Promise { + const { uri, playQueueId } = optionalParams; + if (title === undefined || type === undefined || smart === undefined) { + throw new Error( + 'The following are required parameters: title,type,smart, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (title) { + queryParams.push(serializeQuery('form', true, 'title', title)); + } + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + if (smart) { + queryParams.push(serializeQuery('form', true, 'smart', smart)); + } + if (uri) { + queryParams.push(serializeQuery('form', true, 'uri', uri)); + } + if (playQueueId) { + queryParams.push(serializeQuery('form', true, 'playQueueID', playQueueId)); + } + const urlEndpoint = '/playlists'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.post( + finalUrl, + { title, type_: type, smart, uri, playQueueID: playQueueId }, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get All Playlists + * @description Get All Playlists given the specified filters. + + * @param optionalParams - Optional parameters + * @param optionalParams.playlistType - limit to a type of playlist. + * @param optionalParams.smart - type of playlists to return (default is all). + * @returns {Promise} - The promise with the result + */ + async getPlaylists( + optionalParams: { playlistType?: PlaylistType; smart?: Smart } = {}, + ): Promise { + const { playlistType, smart } = optionalParams; + + const queryParams: string[] = []; + if (playlistType) { + queryParams.push(serializeQuery('form', true, 'playlistType', playlistType)); + } + if (smart) { + queryParams.push(serializeQuery('form', true, 'smart', smart)); + } + const urlEndpoint = '/playlists/all'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Retrieve Playlist + * @description Gets detailed metadata for a playlist. A playlist for many purposes (rating, editing metadata, tagging), can be treated like a regular metadata item: +Smart playlist details contain the `content` attribute. This is the content URI for the generator. This can then be parsed by a client to provide smart playlist editing. + + + * @param playlistID the ID of the playlist + * @returns {Promise} - The promise with the result + */ + async getPlaylist(playlistId: number): Promise { + if (playlistId === undefined) { + throw new Error('The following parameter is required: playlistId, cannot be empty or blank'); + } + let urlEndpoint = '/playlists/{playlistID}'; + urlEndpoint = urlEndpoint.replace( + '{playlistID}', + encodeURIComponent(serializePath('simple', false, playlistId, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Update a Playlist + * @description From PMS version 1.9.1 clients can also edit playlist metadata using this endpoint as they would via `PUT /library/metadata/{playlistID}` + + + * @param playlistID the ID of the playlist + * @returns {Promise} - The promise with the result + */ + async updatePlaylist(playlistId: number): Promise { + if (playlistId === undefined) { + throw new Error('The following parameter is required: playlistId, cannot be empty or blank'); + } + let urlEndpoint = '/playlists/{playlistID}'; + urlEndpoint = urlEndpoint.replace( + '{playlistID}', + encodeURIComponent(serializePath('simple', false, playlistId, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.put( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Deletes a Playlist + * @description This endpoint will delete a playlist + + + * @param playlistID the ID of the playlist + * @returns {Promise} - The promise with the result + */ + async deletePlaylist(playlistId: number): Promise { + if (playlistId === undefined) { + throw new Error('The following parameter is required: playlistId, cannot be empty or blank'); + } + let urlEndpoint = '/playlists/{playlistID}'; + urlEndpoint = urlEndpoint.replace( + '{playlistID}', + encodeURIComponent(serializePath('simple', false, playlistId, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Retrieve Playlist Contents + * @description Gets the contents of a playlist. Should be paged by clients via standard mechanisms. +By default leaves are returned (e.g. episodes, movies). In order to return other types you can use the `type` parameter. +For example, you could use this to display a list of recently added albums vis a smart playlist. +Note that for dumb playlists, items have a `playlistItemID` attribute which is used for deleting or moving items. + + + * @param playlistID the ID of the playlist + * @param type_ the metadata type of the item to return + * @returns {Promise} - The promise with the result + */ + async getPlaylistContents(playlistId: number, type: number): Promise { + if (playlistId === undefined || type === undefined) { + throw new Error( + 'The following are required parameters: playlistId,type, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + let urlEndpoint = '/playlists/{playlistID}/items'; + urlEndpoint = urlEndpoint.replace( + '{playlistID}', + encodeURIComponent(serializePath('simple', false, playlistId, undefined)), + ); + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Adding to a Playlist + * @description Adds a generator to a playlist, same parameters as the POST above. With a dumb playlist, this adds the specified items to the playlist. +With a smart playlist, passing a new `uri` parameter replaces the rules for the playlist. Returns the playlist. + + + * @param playlistID the ID of the playlist + * @param uri the content URI for the playlist + * @param playQueueID the play queue to add to a playlist + * @returns {Promise} - The promise with the result + */ + async addPlaylistContents(playlistId: number, uri: string, playQueueId: number): Promise { + if (playlistId === undefined || uri === undefined || playQueueId === undefined) { + throw new Error( + 'The following are required parameters: playlistId,uri,playQueueId, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + let urlEndpoint = '/playlists/{playlistID}/items'; + urlEndpoint = urlEndpoint.replace( + '{playlistID}', + encodeURIComponent(serializePath('simple', false, playlistId, undefined)), + ); + if (uri) { + queryParams.push(serializeQuery('form', true, 'uri', uri)); + } + if (playQueueId) { + queryParams.push(serializeQuery('form', true, 'playQueueID', playQueueId)); + } + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.put( + finalUrl, + { uri, playQueueID: playQueueId }, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Delete Playlist Contents + * @description Clears a playlist, only works with dumb playlists. Returns the playlist. + + + * @param playlistID the ID of the playlist + * @returns {Promise} - The promise with the result + */ + async clearPlaylistContents(playlistId: number): Promise { + if (playlistId === undefined) { + throw new Error('The following parameter is required: playlistId, cannot be empty or blank'); + } + let urlEndpoint = '/playlists/{playlistID}/items'; + urlEndpoint = urlEndpoint.replace( + '{playlistID}', + encodeURIComponent(serializePath('simple', false, playlistId, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Upload Playlist + * @description Imports m3u playlists by passing a path on the server to scan for m3u-formatted playlist files, or a path to a single playlist file. + + + * @param path absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server. +If the `path` argument is a directory, that path will be scanned for playlist files to be processed. +Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it. +The GUID of each playlist is based on the filename. +If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it. +The GUID of each playlist is based on the filename. + + * @param force force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist. +The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded. + + * @returns {Promise} - The promise with the result + */ + async uploadPlaylist(path: string, force: Force): Promise { + if (path === undefined || force === undefined) { + throw new Error( + 'The following are required parameters: path,force, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (path) { + queryParams.push(serializeQuery('form', true, 'path', path)); + } + if (force) { + queryParams.push(serializeQuery('form', true, 'force', force)); + } + const urlEndpoint = '/playlists/upload'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.post( + finalUrl, + { path, force }, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/playlists/index.ts b/src/services/playlists/index.ts new file mode 100644 index 00000000..69ce5b73 --- /dev/null +++ b/src/services/playlists/index.ts @@ -0,0 +1,4 @@ +export type { Force } from './models/Force'; +export type { PlaylistType } from './models/PlaylistType'; +export type { Smart } from './models/Smart'; +export type { Type } from './models/Type'; diff --git a/src/services/playlists/models/Force.ts b/src/services/playlists/models/Force.ts new file mode 100644 index 00000000..523a7b36 --- /dev/null +++ b/src/services/playlists/models/Force.ts @@ -0,0 +1 @@ +export type Force = 0 | 1; diff --git a/src/services/playlists/models/PlaylistType.ts b/src/services/playlists/models/PlaylistType.ts new file mode 100644 index 00000000..14d4d794 --- /dev/null +++ b/src/services/playlists/models/PlaylistType.ts @@ -0,0 +1 @@ +export type PlaylistType = 'audio' | 'video' | 'photo'; diff --git a/src/services/playlists/models/Smart.ts b/src/services/playlists/models/Smart.ts new file mode 100644 index 00000000..a119be22 --- /dev/null +++ b/src/services/playlists/models/Smart.ts @@ -0,0 +1 @@ +export type Smart = 0 | 1; diff --git a/src/services/playlists/models/Type.ts b/src/services/playlists/models/Type.ts new file mode 100644 index 00000000..d4756d68 --- /dev/null +++ b/src/services/playlists/models/Type.ts @@ -0,0 +1 @@ +export type Type = 'audio' | 'video' | 'photo'; diff --git a/src/services/search/Search.ts b/src/services/search/Search.ts new file mode 100644 index 00000000..1872a239 --- /dev/null +++ b/src/services/search/Search.ts @@ -0,0 +1,149 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { GetSearchResultsResponse } from './models/GetSearchResultsResponse'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class SearchService extends BaseService { + /** + * @summary Perform a search + * @description This endpoint performs a search across all library sections, or a single section, and returns matches as hubs, split up by type. It performs spell checking, looks for partial matches, and orders the hubs based on quality of results. In addition, based on matches, it will return other related matches (e.g. for a genre match, it may return movies in that genre, or for an actor match, movies with that actor). + +In the response's items, the following extra attributes are returned to further describe or disambiguate the result: + +- `reason`: The reason for the result, if not because of a direct search term match; can be either: + - `section`: There are multiple identical results from different sections. + - `originalTitle`: There was a search term match from the original title field (sometimes those can be very different or in a foreign language). + - ``: If the reason for the result is due to a result in another hub, the source hub identifier is returned. For example, if the search is for "dylan" then Bob Dylan may be returned as an artist result, an a few of his albums returned as album results with a reason code of `artist` (the identifier of that particular hub). Or if the search is for "arnold", there might be movie results returned with a reason of `actor` +- `reasonTitle`: The string associated with the reason code. For a section reason, it'll be the section name; For a hub identifier, it'll be a string associated with the match (e.g. `Arnold Schwarzenegger` for movies which were returned because the search was for "arnold"). +- `reasonID`: The ID of the item associated with the reason for the result. This might be a section ID, a tag ID, an artist ID, or a show ID. + +This request is intended to be very fast, and called as the user types. + + + * @param query The query term + * @param optionalParams - Optional parameters + * @param optionalParams.sectionId - This gives context to the search, and can result in re-ordering of search result hubs + * @param optionalParams.limit - The number of items to return per hub + * @returns {Promise} - The promise with the result + */ + async performSearch( + query: string, + optionalParams: { sectionId?: number; limit?: number } = {}, + ): Promise { + const { sectionId, limit } = optionalParams; + if (query === undefined) { + throw new Error('The following parameter is required: query, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (query) { + queryParams.push(serializeQuery('form', true, 'query', query)); + } + if (sectionId) { + queryParams.push(serializeQuery('form', true, 'sectionId', sectionId)); + } + if (limit) { + queryParams.push(serializeQuery('form', true, 'limit', limit)); + } + const urlEndpoint = '/hubs/search'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Perform a voice search + * @description This endpoint performs a search specifically tailored towards voice or other imprecise input which may work badly with the substring and spell-checking heuristics used by the `/hubs/search` endpoint. +It uses a [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) heuristic to search titles, and as such is much slower than the other search endpoint. +Whenever possible, clients should limit the search to the appropriate type. +Results, as well as their containing per-type hubs, contain a `distance` attribute which can be used to judge result quality. + + + * @param query The query term + * @param optionalParams - Optional parameters + * @param optionalParams.sectionId - This gives context to the search, and can result in re-ordering of search result hubs + * @param optionalParams.limit - The number of items to return per hub + * @returns {Promise} - The promise with the result + */ + async performVoiceSearch( + query: string, + optionalParams: { sectionId?: number; limit?: number } = {}, + ): Promise { + const { sectionId, limit } = optionalParams; + if (query === undefined) { + throw new Error('The following parameter is required: query, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (query) { + queryParams.push(serializeQuery('form', true, 'query', query)); + } + if (sectionId) { + queryParams.push(serializeQuery('form', true, 'sectionId', sectionId)); + } + if (limit) { + queryParams.push(serializeQuery('form', true, 'limit', limit)); + } + const urlEndpoint = '/hubs/search/voice'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Search Results + * @description This will search the database for the string provided. + + * @param query The search query string to use + * @returns {Promise>} - The promise with the result + */ + async getSearchResults(query: string): Promise> { + if (query === undefined) { + throw new Error('The following parameter is required: query, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (query) { + queryParams.push(serializeQuery('form', true, 'query', query)); + } + const urlEndpoint = '/search'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/search/index.ts b/src/services/search/index.ts new file mode 100644 index 00000000..38cf08ad --- /dev/null +++ b/src/services/search/index.ts @@ -0,0 +1 @@ +export type { GetSearchResultsResponse } from './models/GetSearchResultsResponse'; diff --git a/src/services/search/models/GetSearchResultsResponse.ts b/src/services/search/models/GetSearchResultsResponse.ts new file mode 100644 index 00000000..76b0db61 --- /dev/null +++ b/src/services/search/models/GetSearchResultsResponse.ts @@ -0,0 +1,85 @@ +export interface GetSearchResultsResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + identifier?: string; + mediaTagPrefix?: string; + mediaTagVersion?: number; + Metadata?: { + allowSync?: boolean; + librarySectionID?: number; + librarySectionTitle?: string; + librarySectionUUID?: string; + personal?: boolean; + sourceTitle?: string; + ratingKey?: number; + key?: string; + guid?: string; + studio?: string; + title?: string; + contentRating?: string; + summary?: string; + rating?: number; + audienceRating?: number; + year?: number; + tagline?: string; + thumb?: string; + art?: string; + duration?: number; + originallyAvailableAt?: string; + addedAt?: number; + updatedAt?: number; + audienceRatingImage?: string; + chapterSource?: string; + primaryExtraKey?: string; + ratingImage?: string; + Media?: { + id?: number; + duration?: number; + bitrate?: number; + width?: number; + height?: number; + aspectRatio?: number; + audioChannels?: number; + audioCodec?: string; + videoCodec?: string; + videoResolution?: number; + container?: string; + videoFrameRate?: string; + audioProfile?: string; + videoProfile?: string; + Part?: { + id?: number; + key?: string; + duration?: number; + file?: string; + size?: number; + audioProfile?: string; + container?: string; + videoProfile?: string; + }[]; + }[]; + Genre?: { + tag?: string; + }[]; + Director?: { + tag?: string; + }[]; + Writer?: { + tag?: string; + }[]; + Country?: { + tag?: string; + }[]; + Role?: { + tag?: string; + }[]; + type_?: string; + }[]; + Provider?: { + key?: string; + title?: string; + type_?: string; + }[]; +} diff --git a/src/services/security/Security.ts b/src/services/security/Security.ts new file mode 100644 index 00000000..f49fabe0 --- /dev/null +++ b/src/services/security/Security.ts @@ -0,0 +1,83 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { SecurityType } from './models/SecurityType'; +import { Scope } from './models/Scope'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class SecurityService extends BaseService { + /** + * @summary Get a Transient Token. + * @description This endpoint provides the caller with a temporary token with the same access level as the caller's token. These tokens are valid for up to 48 hours and are destroyed if the server instance is restarted. + + + * @param type_ `delegation` - This is the only supported `type` parameter. + * @param scope `all` - This is the only supported `scope` parameter. + * @returns {Promise} - The promise with the result + */ + async getTransientToken(type: SecurityType, scope: Scope): Promise { + if (type === undefined || scope === undefined) { + throw new Error( + 'The following are required parameters: type,scope, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (type) { + queryParams.push(serializeQuery('form', true, 'type_', type)); + } + if (scope) { + queryParams.push(serializeQuery('form', true, 'scope', scope)); + } + const urlEndpoint = '/security/token'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Source Connection Information + * @description If a caller requires connection details and a transient token for a source that is known to the server, for example a cloud media provider or shared PMS, then this endpoint can be called. This endpoint is only accessible with either an admin token or a valid transient token generated from an admin token. +Note: requires Plex Media Server >= 1.15.4. + + + * @param source The source identifier with an included prefix. + * @returns {Promise} - The promise with the result + */ + async getSourceConnectionInformation(source: string): Promise { + if (source === undefined) { + throw new Error('The following parameter is required: source, cannot be empty or blank'); + } + const queryParams: string[] = []; + if (source) { + queryParams.push(serializeQuery('form', true, 'source', source)); + } + const urlEndpoint = '/security/resources'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/security/index.ts b/src/services/security/index.ts new file mode 100644 index 00000000..d8d98bf9 --- /dev/null +++ b/src/services/security/index.ts @@ -0,0 +1,2 @@ +export type { Scope } from './models/Scope'; +export type { SecurityType } from './models/SecurityType'; diff --git a/src/services/security/models/Scope.ts b/src/services/security/models/Scope.ts new file mode 100644 index 00000000..4548fa17 --- /dev/null +++ b/src/services/security/models/Scope.ts @@ -0,0 +1 @@ +export type Scope = 'all'; diff --git a/src/services/security/models/SecurityType.ts b/src/services/security/models/SecurityType.ts new file mode 100644 index 00000000..f0d559fd --- /dev/null +++ b/src/services/security/models/SecurityType.ts @@ -0,0 +1 @@ +export type SecurityType = 'delegation'; diff --git a/src/services/server/Server.ts b/src/services/server/Server.ts new file mode 100644 index 00000000..1c7a5115 --- /dev/null +++ b/src/services/server/Server.ts @@ -0,0 +1,259 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { GetServerCapabilitiesResponse } from './models/GetServerCapabilitiesResponse'; +import { GetAvailableClientsResponse } from './models/GetAvailableClientsResponse'; +import { GetDevicesResponse } from './models/GetDevicesResponse'; +import { GetServerIdentityResponse } from './models/GetServerIdentityResponse'; +import { GetMyPlexAccountResponse } from './models/GetMyPlexAccountResponse'; +import { MinSize } from './models/MinSize'; +import { Upscale } from './models/Upscale'; +import { GetServerListResponse } from './models/GetServerListResponse'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class ServerService extends BaseService { + /** + * @summary Server Capabilities + * @description Server Capabilities + + * @returns {Promise>} - The promise with the result + */ + async getServerCapabilities(): Promise> { + const urlEndpoint = '/'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Server Preferences + * @description Get Server Preferences + + * @returns {Promise} - The promise with the result + */ + async getServerPreferences(): Promise { + const urlEndpoint = '/:/prefs'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Available Clients + * @description Get Available Clients + + * @returns {Promise>} - The promise with the result + */ + async getAvailableClients(): Promise> { + const urlEndpoint = '/clients'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Devices + * @description Get Devices + + * @returns {Promise>} - The promise with the result + */ + async getDevices(): Promise> { + const urlEndpoint = '/devices'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Server Identity + * @description Get Server Identity + + * @returns {Promise>} - The promise with the result + */ + async getServerIdentity(): Promise> { + const urlEndpoint = '/identity'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get MyPlex Account + * @description Returns MyPlex Account Information + + * @returns {Promise>} - The promise with the result + */ + async getMyPlexAccount(): Promise> { + const urlEndpoint = '/myplex/account'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get a Resized Photo + * @description Plex's Photo transcoder is used throughout the service to serve images at specified sizes. + + + * @param width The width for the resized photo + * @param height The height for the resized photo + * @param opacity The opacity for the resized photo + * @param blur The width for the resized photo + * @param minSize images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. + * @param upscale allow images to be resized beyond native dimensions. + * @param url path to image within Plex + * @returns {Promise} - The promise with the result + */ + async getResizedPhoto( + width: number, + height: number, + opacity: number, + blur: number, + minSize: MinSize, + upscale: Upscale, + url: string, + ): Promise { + if ( + width === undefined || + height === undefined || + opacity === undefined || + blur === undefined || + minSize === undefined || + upscale === undefined || + url === undefined + ) { + throw new Error( + 'The following are required parameters: width,height,opacity,blur,minSize,upscale,url, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (width) { + queryParams.push(serializeQuery('form', true, 'width', width)); + } + if (height) { + queryParams.push(serializeQuery('form', true, 'height', height)); + } + if (opacity) { + queryParams.push(serializeQuery('form', true, 'opacity', opacity)); + } + if (blur) { + queryParams.push(serializeQuery('form', true, 'blur', blur)); + } + if (minSize) { + queryParams.push(serializeQuery('form', true, 'minSize', minSize)); + } + if (upscale) { + queryParams.push(serializeQuery('form', true, 'upscale', upscale)); + } + if (url) { + queryParams.push(serializeQuery('form', true, 'url', url)); + } + const urlEndpoint = '/photo/:/transcode'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Server List + * @description Get Server List + + * @returns {Promise>} - The promise with the result + */ + async getServerList(): Promise> { + const urlEndpoint = '/servers'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/server/index.ts b/src/services/server/index.ts new file mode 100644 index 00000000..adedc9da --- /dev/null +++ b/src/services/server/index.ts @@ -0,0 +1,8 @@ +export type { GetAvailableClientsResponse } from './models/GetAvailableClientsResponse'; +export type { GetDevicesResponse } from './models/GetDevicesResponse'; +export type { GetMyPlexAccountResponse } from './models/GetMyPlexAccountResponse'; +export type { GetServerCapabilitiesResponse } from './models/GetServerCapabilitiesResponse'; +export type { GetServerIdentityResponse } from './models/GetServerIdentityResponse'; +export type { GetServerListResponse } from './models/GetServerListResponse'; +export type { MinSize } from './models/MinSize'; +export type { Upscale } from './models/Upscale'; diff --git a/src/services/server/models/GetAvailableClientsResponse.ts b/src/services/server/models/GetAvailableClientsResponse.ts new file mode 100644 index 00000000..7e8de603 --- /dev/null +++ b/src/services/server/models/GetAvailableClientsResponse.ts @@ -0,0 +1,20 @@ +export type GetAvailableClientsResponse = { + MediaContainer?: MediaContainer; +}[]; + +interface MediaContainer { + size?: number; + Server?: { + name?: string; + host?: string; + address?: string; + port?: number; + machineIdentifier?: string; + version?: string; + protocol?: string; + product?: string; + deviceClass?: string; + protocolVersion?: number; + protocolCapabilities?: string; + }[]; +} diff --git a/src/services/server/models/GetDevicesResponse.ts b/src/services/server/models/GetDevicesResponse.ts new file mode 100644 index 00000000..86ae4a1b --- /dev/null +++ b/src/services/server/models/GetDevicesResponse.ts @@ -0,0 +1,14 @@ +export interface GetDevicesResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + identifier?: string; + Device?: { + id?: number; + name?: string; + platform?: string; + clientIdentifier?: string; + createdAt?: number; + }[]; +} diff --git a/src/services/server/models/GetMyPlexAccountResponse.ts b/src/services/server/models/GetMyPlexAccountResponse.ts new file mode 100644 index 00000000..7bddfcca --- /dev/null +++ b/src/services/server/models/GetMyPlexAccountResponse.ts @@ -0,0 +1,17 @@ +export interface GetMyPlexAccountResponse { + MyPlex?: MyPlex; +} +interface MyPlex { + authToken?: string; + username?: string; + mappingState?: string; + mappingError?: string; + signInState?: string; + publicAddress?: string; + publicPort?: number; + privateAddress?: string; + privatePort?: number; + subscriptionFeatures?: string; + subscriptionActive?: boolean; + subscriptionState?: string; +} diff --git a/src/services/server/models/GetServerCapabilitiesResponse.ts b/src/services/server/models/GetServerCapabilitiesResponse.ts new file mode 100644 index 00000000..06f1f824 --- /dev/null +++ b/src/services/server/models/GetServerCapabilitiesResponse.ts @@ -0,0 +1,60 @@ +export interface GetServerCapabilitiesResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + allowCameraUpload?: boolean; + allowChannelAccess?: boolean; + allowMediaDeletion?: boolean; + allowSharing?: boolean; + allowSync?: boolean; + allowTuners?: boolean; + backgroundProcessing?: boolean; + certificate?: boolean; + companionProxy?: boolean; + countryCode?: string; + diagnostics?: string; + eventStream?: boolean; + friendlyName?: string; + hubSearch?: boolean; + itemClusters?: boolean; + livetv?: number; + machineIdentifier?: string; + mediaProviders?: boolean; + multiuser?: boolean; + musicAnalysis?: number; + myPlex?: boolean; + myPlexMappingState?: string; + myPlexSigninState?: string; + myPlexSubscription?: boolean; + myPlexUsername?: string; + offlineTranscode?: number; + ownerFeatures?: string; + photoAutoTag?: boolean; + platform?: string; + platformVersion?: string; + pluginHost?: boolean; + pushNotifications?: boolean; + readOnlyLibraries?: boolean; + streamingBrainABRVersion?: number; + streamingBrainVersion?: number; + sync?: boolean; + transcoderActiveVideoSessions?: number; + transcoderAudio?: boolean; + transcoderLyrics?: boolean; + transcoderPhoto?: boolean; + transcoderSubtitles?: boolean; + transcoderVideo?: boolean; + transcoderVideoBitrates?: string; + transcoderVideoQualities?: string; + transcoderVideoResolutions?: string; + updatedAt?: number; + updater?: boolean; + version?: string; + voiceSearch?: boolean; + Directory?: { + count?: number; + key?: string; + title?: string; + }[]; +} diff --git a/src/services/server/models/GetServerIdentityResponse.ts b/src/services/server/models/GetServerIdentityResponse.ts new file mode 100644 index 00000000..aefbccd7 --- /dev/null +++ b/src/services/server/models/GetServerIdentityResponse.ts @@ -0,0 +1,9 @@ +export interface GetServerIdentityResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + claimed?: boolean; + machineIdentifier?: string; + version?: string; +} diff --git a/src/services/server/models/GetServerListResponse.ts b/src/services/server/models/GetServerListResponse.ts new file mode 100644 index 00000000..28937c85 --- /dev/null +++ b/src/services/server/models/GetServerListResponse.ts @@ -0,0 +1,14 @@ +export interface GetServerListResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + Server?: { + name?: string; + host?: string; + address?: string; + port?: number; + machineIdentifier?: string; + version?: string; + }[]; +} diff --git a/src/services/server/models/MinSize.ts b/src/services/server/models/MinSize.ts new file mode 100644 index 00000000..9f0e1fe2 --- /dev/null +++ b/src/services/server/models/MinSize.ts @@ -0,0 +1 @@ +export type MinSize = 0 | 1; diff --git a/src/services/server/models/Upscale.ts b/src/services/server/models/Upscale.ts new file mode 100644 index 00000000..5252b310 --- /dev/null +++ b/src/services/server/models/Upscale.ts @@ -0,0 +1 @@ +export type Upscale = 0 | 1; diff --git a/src/services/sessions/Sessions.ts b/src/services/sessions/Sessions.ts new file mode 100644 index 00000000..fd69b8d3 --- /dev/null +++ b/src/services/sessions/Sessions.ts @@ -0,0 +1,113 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { GetTranscodeSessionsResponse } from './models/GetTranscodeSessionsResponse'; + +import { serializePath } from '../../http/QuerySerializer'; + +export class SessionsService extends BaseService { + /** + * @summary Get Active Sessions + * @description This will retrieve the "Now Playing" Information of the PMS. + + * @returns {Promise} - The promise with the result + */ + async getSessions(): Promise { + const urlEndpoint = '/status/sessions'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Session History + * @description This will Retrieve a listing of all history views. + + * @returns {Promise} - The promise with the result + */ + async getSessionHistory(): Promise { + const urlEndpoint = '/status/sessions/history/all'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get Transcode Sessions + * @description Get Transcode Sessions + + * @returns {Promise>} - The promise with the result + */ + async getTranscodeSessions(): Promise> { + const urlEndpoint = '/transcode/sessions'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Stop a Transcode Session + * @description Stop a Transcode Session + + * @param sessionKey the Key of the transcode session to stop + * @returns {Promise} - The promise with the result + */ + async stopTranscodeSession(sessionKey: string): Promise { + if (sessionKey === undefined) { + throw new Error('The following parameter is required: sessionKey, cannot be empty or blank'); + } + let urlEndpoint = '/transcode/sessions/{sessionKey}'; + urlEndpoint = urlEndpoint.replace( + '{sessionKey}', + encodeURIComponent(serializePath('simple', false, sessionKey, undefined)), + ); + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.delete( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/sessions/index.ts b/src/services/sessions/index.ts new file mode 100644 index 00000000..7347ded8 --- /dev/null +++ b/src/services/sessions/index.ts @@ -0,0 +1 @@ +export type { GetTranscodeSessionsResponse } from './models/GetTranscodeSessionsResponse'; diff --git a/src/services/sessions/models/GetTranscodeSessionsResponse.ts b/src/services/sessions/models/GetTranscodeSessionsResponse.ts new file mode 100644 index 00000000..19e8bb03 --- /dev/null +++ b/src/services/sessions/models/GetTranscodeSessionsResponse.ts @@ -0,0 +1,30 @@ +export interface GetTranscodeSessionsResponse { + MediaContainer?: MediaContainer; +} +interface MediaContainer { + size?: number; + TranscodeSession?: { + key?: string; + throttled?: boolean; + complete?: boolean; + progress?: number; + size?: number; + speed?: number; + error?: boolean; + duration?: number; + context?: string; + sourceVideoCodec?: string; + sourceAudioCodec?: string; + videoDecision?: string; + audioDecision?: string; + protocol?: string; + container?: string; + videoCodec?: string; + audioCodec?: string; + audioChannels?: number; + transcodeHwRequested?: boolean; + timeStamp?: number; + maxOffsetAvailable?: number; + minOffsetAvailable?: number; + }[]; +} diff --git a/src/services/updater/Updater.ts b/src/services/updater/Updater.ts new file mode 100644 index 00000000..d7cdfd55 --- /dev/null +++ b/src/services/updater/Updater.ts @@ -0,0 +1,106 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { Download } from './models/Download'; +import { Tonight } from './models/Tonight'; +import { Skip } from './models/Skip'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class UpdaterService extends BaseService { + /** + * @summary Querying status of updates + * @description Querying status of updates + + * @returns {Promise} - The promise with the result + */ + async getUpdateStatus(): Promise { + const urlEndpoint = '/updater/status'; + const finalUrl = `${this.baseUrl + urlEndpoint}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Checking for updates + * @description Checking for updates + + * @param optionalParams - Optional parameters + * @param optionalParams.download - Indicate that you want to start download any updates found. + * @returns {Promise} - The promise with the result + */ + async checkForUpdates(optionalParams: { download?: Download } = {}): Promise { + const { download } = optionalParams; + + const queryParams: string[] = []; + if (download) { + queryParams.push(serializeQuery('form', true, 'download', download)); + } + const urlEndpoint = '/updater/check'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.put( + finalUrl, + { download }, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Apply Updates + * @description Note that these two parameters are effectively mutually exclusive. The `tonight` parameter takes precedence and `skip` will be ignored if `tonight` is also passed + + + * @param optionalParams - Optional parameters + * @param optionalParams.tonight - Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install + * @param optionalParams.skip - Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. + * @returns {Promise} - The promise with the result + */ + async applyUpdates(optionalParams: { tonight?: Tonight; skip?: Skip } = {}): Promise { + const { tonight, skip } = optionalParams; + + const queryParams: string[] = []; + if (tonight) { + queryParams.push(serializeQuery('form', true, 'tonight', tonight)); + } + if (skip) { + queryParams.push(serializeQuery('form', true, 'skip', skip)); + } + const urlEndpoint = '/updater/apply'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.put( + finalUrl, + { tonight, skip }, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/updater/index.ts b/src/services/updater/index.ts new file mode 100644 index 00000000..9518ba1d --- /dev/null +++ b/src/services/updater/index.ts @@ -0,0 +1,3 @@ +export type { Download } from './models/Download'; +export type { Skip } from './models/Skip'; +export type { Tonight } from './models/Tonight'; diff --git a/src/services/updater/models/Download.ts b/src/services/updater/models/Download.ts new file mode 100644 index 00000000..5b99d1f9 --- /dev/null +++ b/src/services/updater/models/Download.ts @@ -0,0 +1 @@ +export type Download = 0 | 1; diff --git a/src/services/updater/models/Skip.ts b/src/services/updater/models/Skip.ts new file mode 100644 index 00000000..963e6cee --- /dev/null +++ b/src/services/updater/models/Skip.ts @@ -0,0 +1 @@ +export type Skip = 0 | 1; diff --git a/src/services/updater/models/Tonight.ts b/src/services/updater/models/Tonight.ts new file mode 100644 index 00000000..1eeb3492 --- /dev/null +++ b/src/services/updater/models/Tonight.ts @@ -0,0 +1 @@ +export type Tonight = 0 | 1; diff --git a/src/services/video/Video.ts b/src/services/video/Video.ts new file mode 100644 index 00000000..64134e35 --- /dev/null +++ b/src/services/video/Video.ts @@ -0,0 +1,235 @@ +import BaseService from '../../BaseService'; + +import Response from '../../http/Response'; + +import { State } from './models/State'; + +import { serializeQuery } from '../../http/QuerySerializer'; + +export class VideoService extends BaseService { + /** + * @summary Start Universal Transcode + * @description Begin a Universal Transcode Session + + * @param hasMDE Whether the media item has MDE + * @param path The path to the media item to transcode + * @param mediaIndex The index of the media item to transcode + * @param partIndex The index of the part to transcode + * @param protocol The protocol to use for the transcode session + * @param optionalParams - Optional parameters + * @param optionalParams.fastSeek - Whether to use fast seek or not + * @param optionalParams.directPlay - Whether to use direct play or not + * @param optionalParams.directStream - Whether to use direct stream or not + * @param optionalParams.subtitleSize - The size of the subtitles + * @param optionalParams.subtites - The subtitles + * @param optionalParams.audioBoost - The audio boost + * @param optionalParams.location - The location of the transcode session + * @param optionalParams.mediaBufferSize - The size of the media buffer + * @param optionalParams.session - The session ID + * @param optionalParams.addDebugOverlay - Whether to add a debug overlay or not + * @param optionalParams.autoAdjustQuality - Whether to auto adjust quality or not + * @returns {Promise} - The promise with the result + */ + async startUniversalTranscode( + hasMde: number, + path: string, + mediaIndex: number, + partIndex: number, + protocol: string, + optionalParams: { + fastSeek?: number; + directPlay?: number; + directStream?: number; + subtitleSize?: number; + subtites?: string; + audioBoost?: number; + location?: string; + mediaBufferSize?: number; + session?: string; + addDebugOverlay?: number; + autoAdjustQuality?: number; + } = {}, + ): Promise { + const { + fastSeek, + directPlay, + directStream, + subtitleSize, + subtites, + audioBoost, + location, + mediaBufferSize, + session, + addDebugOverlay, + autoAdjustQuality, + } = optionalParams; + if ( + hasMde === undefined || + path === undefined || + mediaIndex === undefined || + partIndex === undefined || + protocol === undefined + ) { + throw new Error( + 'The following are required parameters: hasMde,path,mediaIndex,partIndex,protocol, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (hasMde) { + queryParams.push(serializeQuery('form', true, 'hasMDE', hasMde)); + } + if (path) { + queryParams.push(serializeQuery('form', true, 'path', path)); + } + if (mediaIndex) { + queryParams.push(serializeQuery('form', true, 'mediaIndex', mediaIndex)); + } + if (partIndex) { + queryParams.push(serializeQuery('form', true, 'partIndex', partIndex)); + } + if (protocol) { + queryParams.push(serializeQuery('form', true, 'protocol', protocol)); + } + if (fastSeek) { + queryParams.push(serializeQuery('form', true, 'fastSeek', fastSeek)); + } + if (directPlay) { + queryParams.push(serializeQuery('form', true, 'directPlay', directPlay)); + } + if (directStream) { + queryParams.push(serializeQuery('form', true, 'directStream', directStream)); + } + if (subtitleSize) { + queryParams.push(serializeQuery('form', true, 'subtitleSize', subtitleSize)); + } + if (subtites) { + queryParams.push(serializeQuery('form', true, 'subtites', subtites)); + } + if (audioBoost) { + queryParams.push(serializeQuery('form', true, 'audioBoost', audioBoost)); + } + if (location) { + queryParams.push(serializeQuery('form', true, 'location', location)); + } + if (mediaBufferSize) { + queryParams.push(serializeQuery('form', true, 'mediaBufferSize', mediaBufferSize)); + } + if (session) { + queryParams.push(serializeQuery('form', true, 'session', session)); + } + if (addDebugOverlay) { + queryParams.push(serializeQuery('form', true, 'addDebugOverlay', addDebugOverlay)); + } + if (autoAdjustQuality) { + queryParams.push(serializeQuery('form', true, 'autoAdjustQuality', autoAdjustQuality)); + } + const urlEndpoint = '/video/:/transcode/universal/start.mpd'; + const urlParams = queryParams.length > 0 ? `?${encodeURI(queryParams.join('&'))}` : ''; + const finalUrl = `${this.baseUrl + urlEndpoint}${urlParams}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } + + /** + * @summary Get the timeline for a media item + * @description Get the timeline for a media item + + * @param ratingKey The rating key of the media item + * @param key The key of the media item to get the timeline for + * @param state The state of the media item + * @param hasMDE Whether the media item has MDE + * @param time The time of the media item + * @param duration The duration of the media item + * @param context The context of the media item + * @param playQueueItemID The play queue item ID of the media item + * @param playBackTime The playback time of the media item + * @param row The row of the media item + * @returns {Promise} - The promise with the result + */ + async getTimeline( + ratingKey: number, + key: string, + state: State, + hasMde: number, + time: number, + duration: number, + context: string, + playQueueItemId: number, + playBackTime: number, + row: number, + ): Promise { + if ( + ratingKey === undefined || + key === undefined || + state === undefined || + hasMde === undefined || + time === undefined || + duration === undefined || + context === undefined || + playQueueItemId === undefined || + playBackTime === undefined || + row === undefined + ) { + throw new Error( + 'The following are required parameters: ratingKey,key,state,hasMde,time,duration,context,playQueueItemId,playBackTime,row, cannot be empty or blank', + ); + } + const queryParams: string[] = []; + if (ratingKey) { + queryParams.push(serializeQuery('form', true, 'ratingKey', ratingKey)); + } + if (key) { + queryParams.push(serializeQuery('form', true, 'key', key)); + } + if (state) { + queryParams.push(serializeQuery('form', true, 'state', state)); + } + if (hasMde) { + queryParams.push(serializeQuery('form', true, 'hasMDE', hasMde)); + } + if (time) { + queryParams.push(serializeQuery('form', true, 'time', time)); + } + if (duration) { + queryParams.push(serializeQuery('form', true, 'duration', duration)); + } + if (context) { + queryParams.push(serializeQuery('form', true, 'context', context)); + } + if (playQueueItemId) { + queryParams.push(serializeQuery('form', true, 'playQueueItemID', playQueueItemId)); + } + if (playBackTime) { + queryParams.push(serializeQuery('form', true, 'playBackTime', playBackTime)); + } + if (row) { + queryParams.push(serializeQuery('form', true, 'row', row)); + } + const urlEndpoint = '/:/timeline'; + const finalUrl = `${this.baseUrl + urlEndpoint}?${encodeURI(queryParams.join('&'))}`; + const response: any = await this.httpClient.get( + finalUrl, + {}, + { + ...this.getAuthorizationHeader(), + }, + true, + ); + const responseModel = { + data: response.data, + headers: response.headers, + }; + return responseModel; + } +} diff --git a/src/services/video/index.ts b/src/services/video/index.ts new file mode 100644 index 00000000..234466e0 --- /dev/null +++ b/src/services/video/index.ts @@ -0,0 +1 @@ +export type { State } from './models/State'; diff --git a/src/services/video/models/State.ts b/src/services/video/models/State.ts new file mode 100644 index 00000000..07f9e46e --- /dev/null +++ b/src/services/video/models/State.ts @@ -0,0 +1 @@ +export type State = 'playing' | 'paused' | 'stopped'; diff --git a/test/services/activities/Activities.test.ts b/test/services/activities/Activities.test.ts new file mode 100644 index 00000000..68b21c5b --- /dev/null +++ b/test/services/activities/Activities.test.ts @@ -0,0 +1,57 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { ActivitiesService } from '../../../src/services/activities/Activities'; + +describe('test ActivitiesService object', () => { + it('should be an object', () => { + expect(typeof ActivitiesService).toBe('function'); + }); +}); + +describe('test Activities', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getServerActivities', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/activities').reply(200, { data: {} }); + return sdk.activities + .getServerActivities() + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test cancelServerActivities', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/activities/ex') + .reply(200, { data: {} }); + return sdk.activities + .cancelServerActivities('ex') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/activities/porro') + .reply(200, { data: {} }); + return expect(async () => await sdk.activities.cancelServerActivities()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/activities/dicta') + .reply(404, { data: {} }); + return expect( + async () => await sdk.activities.cancelServerActivities('dicta'), + ).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/butler/Butler.test.ts b/test/services/butler/Butler.test.ts new file mode 100644 index 00000000..98de462f --- /dev/null +++ b/test/services/butler/Butler.test.ts @@ -0,0 +1,90 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { ButlerService } from '../../../src/services/butler/Butler'; + +describe('test ButlerService object', () => { + it('should be an object', () => { + expect(typeof ButlerService).toBe('function'); + }); +}); + +describe('test Butler', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getButlerTasks', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/butler').reply(200, { data: {} }); + return sdk.butler.getButlerTasks().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test startAllTasks', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').post('/butler').reply(200, { data: {} }); + return sdk.butler.startAllTasks().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test stopAllTasks', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').delete('/butler').reply(200, { data: {} }); + return sdk.butler.stopAllTasks().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test startTask', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/butler/doloribus') + .reply(200, { data: {} }); + return sdk.butler + .startTask('doloribus') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/butler/beatae') + .reply(200, { data: {} }); + return expect(async () => await sdk.butler.startTask()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/butler/aliquam') + .reply(404, { data: {} }); + return expect(async () => await sdk.butler.startTask('aliquam')).rejects.toThrow(); + }); + }); + + describe('test stopTask', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/butler/beatae') + .reply(200, { data: {} }); + return sdk.butler.stopTask('beatae').then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/butler/necessitatibus') + .reply(200, { data: {} }); + return expect(async () => await sdk.butler.stopTask()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/butler/officia') + .reply(404, { data: {} }); + return expect(async () => await sdk.butler.stopTask('officia')).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/hubs/Hubs.test.ts b/test/services/hubs/Hubs.test.ts new file mode 100644 index 00000000..d164da2b --- /dev/null +++ b/test/services/hubs/Hubs.test.ts @@ -0,0 +1,59 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { HubsService } from '../../../src/services/hubs/Hubs'; + +describe('test HubsService object', () => { + it('should be an object', () => { + expect(typeof HubsService).toBe('function'); + }); +}); + +describe('test Hubs', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getGlobalHubs', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs?count=5&onlyTransient=3') + .reply(200, { data: {} }); + return sdk.hubs + .getGlobalHubs({ count: 5, onlyTransient: 3 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getLibraryHubs', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/sections/2?count=9&onlyTransient=2') + .reply(200, { data: {} }); + return sdk.hubs + .getLibraryHubs(2, { count: 9, onlyTransient: 2 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/sections/6?count=7&onlyTransient=5') + .reply(200, { data: {} }); + return expect(async () => await sdk.hubs.getLibraryHubs()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/sections/2?count=6&onlyTransient=8') + .reply(404, { data: {} }); + return expect( + async () => await sdk.hubs.getLibraryHubs(2, { count: 6, onlyTransient: 8 }), + ).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/library/Library.test.ts b/test/services/library/Library.test.ts new file mode 100644 index 00000000..8f408a6a --- /dev/null +++ b/test/services/library/Library.test.ts @@ -0,0 +1,277 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { LibraryService } from '../../../src/services/library/Library'; + +describe('test LibraryService object', () => { + it('should be an object', () => { + expect(typeof LibraryService).toBe('function'); + }); +}); + +describe('test Library', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getFileHash', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/hashes?url=officia&type_=7') + .reply(200, { data: {} }); + return sdk.library + .getFileHash('officia', { type: 7 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/hashes?url=molestiae&type_=3') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getFileHash()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/hashes?url=perferendis&type_=6') + .reply(404, { data: {} }); + return expect( + async () => await sdk.library.getFileHash('perferendis', { type: 6 }), + ).rejects.toThrow(); + }); + }); + + describe('test getRecentlyAdded', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/recentlyAdded') + .reply(200, { data: {} }); + return sdk.library.getRecentlyAdded().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getLibraries', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections') + .reply(200, { data: {} }); + return sdk.library.getLibraries().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getLibrary', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/3?includeDetails=2') + .reply(200, { data: {} }); + return sdk.library + .getLibrary(3, { includeDetails: 2 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/4?includeDetails=3') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getLibrary()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/9?includeDetails=2') + .reply(404, { data: {} }); + return expect( + async () => await sdk.library.getLibrary(9, { includeDetails: 2 }), + ).rejects.toThrow(); + }); + }); + + describe('test deleteLibrary', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/library/sections/2') + .reply(200, { data: {} }); + return sdk.library.deleteLibrary(2).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/library/sections/7') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.deleteLibrary()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/library/sections/8') + .reply(404, { data: {} }); + return expect(async () => await sdk.library.deleteLibrary(8)).rejects.toThrow(); + }); + }); + + describe('test getLibraryItems', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/1/all?type_=6&filter=consequuntur') + .reply(200, { data: {} }); + return sdk.library + .getLibraryItems(1, { type: 6, filter: 'consequuntur' }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/1/all?type_=5&filter=reiciendis') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getLibraryItems()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/4/all?type_=5&filter=similique') + .reply(404, { data: {} }); + return expect( + async () => await sdk.library.getLibraryItems(4, { type: 5, filter: 'similique' }), + ).rejects.toThrow(); + }); + }); + + describe('test refreshLibrary', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/4/refresh') + .reply(200, { data: {} }); + return sdk.library.refreshLibrary(4).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/6/refresh') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.refreshLibrary()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/9/refresh') + .reply(404, { data: {} }); + return expect(async () => await sdk.library.refreshLibrary(9)).rejects.toThrow(); + }); + }); + + describe('test getLatestLibraryItems', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/6/latest?type_=1&filter=repudiandae') + .reply(200, { data: {} }); + return sdk.library + .getLatestLibraryItems(6, 1, { filter: 'repudiandae' }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/7/latest?type_=1&filter=repellendus') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getLatestLibraryItems()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/1/latest?type_=2&filter=consequuntur') + .reply(404, { data: {} }); + return expect( + async () => await sdk.library.getLatestLibraryItems(1, 2, { filter: 'consequuntur' }), + ).rejects.toThrow(); + }); + }); + + describe('test getCommonLibraryItems', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/2/common?type_=4&filter=neque') + .reply(200, { data: {} }); + return sdk.library + .getCommonLibraryItems(2, 4, { filter: 'neque' }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/7/common?type_=4&filter=dolorum') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getCommonLibraryItems()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/sections/9/common?type_=2&filter=iusto') + .reply(404, { data: {} }); + return expect( + async () => await sdk.library.getCommonLibraryItems(9, 2, { filter: 'iusto' }), + ).rejects.toThrow(); + }); + }); + + describe('test getMetadata', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/metadata/6') + .reply(200, { data: {} }); + return sdk.library.getMetadata(6).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/metadata/1') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getMetadata()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/metadata/6') + .reply(404, { data: {} }); + return expect(async () => await sdk.library.getMetadata(6)).rejects.toThrow(); + }); + }); + + describe('test getMetadataChildren', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/metadata/3/children') + .reply(200, { data: {} }); + return sdk.library + .getMetadataChildren(3) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/metadata/1/children') + .reply(200, { data: {} }); + return expect(async () => await sdk.library.getMetadataChildren()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/metadata/8/children') + .reply(404, { data: {} }); + return expect(async () => await sdk.library.getMetadataChildren(8)).rejects.toThrow(); + }); + }); + + describe('test getOnDeck', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/library/onDeck') + .reply(200, { data: {} }); + return sdk.library.getOnDeck().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); +}); diff --git a/test/services/log/Log.test.ts b/test/services/log/Log.test.ts new file mode 100644 index 00000000..266499ef --- /dev/null +++ b/test/services/log/Log.test.ts @@ -0,0 +1,60 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { LogService } from '../../../src/services/log/Log'; + +describe('test LogService object', () => { + it('should be an object', () => { + expect(typeof LogService).toBe('function'); + }); +}); + +describe('test Log', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test logLine', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/log?level=2&message=esse&source=cum') + .reply(200, { data: {} }); + return sdk.log + .logLine(2, 'esse', 'cum') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/log?level=3&message=iste&source=ullam') + .reply(200, { data: {} }); + return expect(async () => await sdk.log.logLine()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/log?level=8&message=tenetur&source=eum') + .reply(404, { data: {} }); + return expect(async () => await sdk.log.logLine(8, 'tenetur', 'eum')).rejects.toThrow(); + }); + }); + + describe('test logMultiLine', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').post('/log').reply(200, { data: {} }); + return sdk.log.logMultiLine().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test enablePaperTrail', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/log/networked').reply(200, { data: {} }); + return sdk.log.enablePaperTrail().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); +}); diff --git a/test/services/media/Media.test.ts b/test/services/media/Media.test.ts new file mode 100644 index 00000000..a95cdee6 --- /dev/null +++ b/test/services/media/Media.test.ts @@ -0,0 +1,94 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { MediaService } from '../../../src/services/media/Media'; + +describe('test MediaService object', () => { + it('should be an object', () => { + expect(typeof MediaService).toBe('function'); + }); +}); + +describe('test Media', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test markPlayed', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/:/scrobble?key=3') + .reply(200, { data: {} }); + return sdk.media.markPlayed(3).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/:/scrobble?key=4') + .reply(200, { data: {} }); + return expect(async () => await sdk.media.markPlayed()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/:/scrobble?key=1') + .reply(404, { data: {} }); + return expect(async () => await sdk.media.markPlayed(1)).rejects.toThrow(); + }); + }); + + describe('test markUnplayed', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/:/unscrobble?key=8') + .reply(200, { data: {} }); + return sdk.media.markUnplayed(8).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/:/unscrobble?key=9') + .reply(200, { data: {} }); + return expect(async () => await sdk.media.markUnplayed()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/:/unscrobble?key=7') + .reply(404, { data: {} }); + return expect(async () => await sdk.media.markUnplayed(7)).rejects.toThrow(); + }); + }); + + describe('test updatePlayProgress', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/:/progress?key=exercitationem&time=6&state=eum') + .reply(200, { data: {} }); + return sdk.media + .updatePlayProgress('exercitationem', 6, 'eum') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/:/progress?key=consectetur&time=1&state=ratione') + .reply(200, { data: {} }); + return expect(async () => await sdk.media.updatePlayProgress()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/:/progress?key=illum&time=1&state=ullam') + .reply(404, { data: {} }); + return expect( + async () => await sdk.media.updatePlayProgress('illum', 1, 'ullam'), + ).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/playlists/Playlists.test.ts b/test/services/playlists/Playlists.test.ts new file mode 100644 index 00000000..144b35e2 --- /dev/null +++ b/test/services/playlists/Playlists.test.ts @@ -0,0 +1,224 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { PlaylistsService } from '../../../src/services/playlists/Playlists'; + +describe('test PlaylistsService object', () => { + it('should be an object', () => { + expect(typeof PlaylistsService).toBe('function'); + }); +}); + +describe('test Playlists', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test createPlaylist', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/playlists?title=ipsum&type_=dicta&smart=6&uri=tenetur&playQueueID=8') + .reply(200, { data: {} }); + return sdk.playlists + .createPlaylist('ipsum', 'dicta', 6, { uri: 'tenetur', playQueueID: 8 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/playlists?title=ab&type_=voluptas&smart=3&uri=ullam&playQueueID=6') + .reply(200, { data: {} }); + return expect(async () => await sdk.playlists.createPlaylist()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/playlists?title=distinctio&type_=nobis&smart=2&uri=aut&playQueueID=7') + .reply(404, { data: {} }); + return expect( + async () => + await sdk.playlists.createPlaylist('distinctio', 'nobis', 2, { + uri: 'aut', + playQueueID: 7, + }), + ).rejects.toThrow(); + }); + }); + + describe('test getPlaylists', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/playlists/all?playlistType=deserunt&smart=3') + .reply(200, { data: {} }); + return sdk.playlists + .getPlaylists({ playlistType: 'deserunt', smart: 3 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getPlaylist', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/playlists/4').reply(200, { data: {} }); + return sdk.playlists.getPlaylist(4).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/playlists/1').reply(200, { data: {} }); + return expect(async () => await sdk.playlists.getPlaylist()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/playlists/5').reply(404, { data: {} }); + return expect(async () => await sdk.playlists.getPlaylist(5)).rejects.toThrow(); + }); + }); + + describe('test updatePlaylist', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').put('/playlists/1').reply(200, { data: {} }); + return sdk.playlists.updatePlaylist(1).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}').put('/playlists/6').reply(200, { data: {} }); + return expect(async () => await sdk.playlists.updatePlaylist()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}').put('/playlists/6').reply(404, { data: {} }); + return expect(async () => await sdk.playlists.updatePlaylist(6)).rejects.toThrow(); + }); + }); + + describe('test deletePlaylist', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/playlists/1') + .reply(200, { data: {} }); + return sdk.playlists.deletePlaylist(1).then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/playlists/3') + .reply(200, { data: {} }); + return expect(async () => await sdk.playlists.deletePlaylist()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/playlists/7') + .reply(404, { data: {} }); + return expect(async () => await sdk.playlists.deletePlaylist(7)).rejects.toThrow(); + }); + }); + + describe('test getPlaylistContents', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/playlists/1/items?type_=4') + .reply(200, { data: {} }); + return sdk.playlists + .getPlaylistContents(1, 4) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/playlists/2/items?type_=8') + .reply(200, { data: {} }); + return expect(async () => await sdk.playlists.getPlaylistContents()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/playlists/9/items?type_=9') + .reply(404, { data: {} }); + return expect(async () => await sdk.playlists.getPlaylistContents(9, 9)).rejects.toThrow(); + }); + }); + + describe('test addPlaylistContents', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .put('/playlists/5/items?uri=repellendus&playQueueID=7') + .reply(200, { data: {} }); + return sdk.playlists + .addPlaylistContents(5, 'repellendus', 7) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .put('/playlists/4/items?uri=ipsum&playQueueID=7') + .reply(200, { data: {} }); + return expect(async () => await sdk.playlists.addPlaylistContents()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .put('/playlists/3/items?uri=iusto&playQueueID=1') + .reply(404, { data: {} }); + return expect( + async () => await sdk.playlists.addPlaylistContents(3, 'iusto', 1), + ).rejects.toThrow(); + }); + }); + + describe('test clearPlaylistContents', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/playlists/8/items') + .reply(200, { data: {} }); + return sdk.playlists + .clearPlaylistContents(8) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/playlists/1/items') + .reply(200, { data: {} }); + return expect(async () => await sdk.playlists.clearPlaylistContents()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/playlists/8/items') + .reply(404, { data: {} }); + return expect(async () => await sdk.playlists.clearPlaylistContents(8)).rejects.toThrow(); + }); + }); + + describe('test uploadPlaylist', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/playlists/upload?path=omnis&force=7') + .reply(200, { data: {} }); + return sdk.playlists + .uploadPlaylist('omnis', 7) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/playlists/upload?path=consequatur&force=7') + .reply(200, { data: {} }); + return expect(async () => await sdk.playlists.uploadPlaylist()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .post('/playlists/upload?path=explicabo&force=3') + .reply(404, { data: {} }); + return expect( + async () => await sdk.playlists.uploadPlaylist('explicabo', 3), + ).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/search/Search.test.ts b/test/services/search/Search.test.ts new file mode 100644 index 00000000..c07ebb23 --- /dev/null +++ b/test/services/search/Search.test.ts @@ -0,0 +1,101 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { SearchService } from '../../../src/services/search/Search'; + +describe('test SearchService object', () => { + it('should be an object', () => { + expect(typeof SearchService).toBe('function'); + }); +}); + +describe('test Search', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test performSearch', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/search?query=exercitationem§ionId=2&limit=2') + .reply(200, { data: {} }); + return sdk.search + .performSearch('exercitationem', { sectionId: 2, limit: 2 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/search?query=officiis§ionId=2&limit=2') + .reply(200, { data: {} }); + return expect(async () => await sdk.search.performSearch()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/search?query=perferendis§ionId=2&limit=1') + .reply(404, { data: {} }); + return expect( + async () => await sdk.search.performSearch('perferendis', { sectionId: 2, limit: 1 }), + ).rejects.toThrow(); + }); + }); + + describe('test performVoiceSearch', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/search/voice?query=minus§ionId=5&limit=1') + .reply(200, { data: {} }); + return sdk.search + .performVoiceSearch('minus', { sectionId: 5, limit: 1 }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/search/voice?query=sed§ionId=1&limit=7') + .reply(200, { data: {} }); + return expect(async () => await sdk.search.performVoiceSearch()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/hubs/search/voice?query=exercitationem§ionId=7&limit=9') + .reply(404, { data: {} }); + return expect( + async () => + await sdk.search.performVoiceSearch('exercitationem', { sectionId: 7, limit: 9 }), + ).rejects.toThrow(); + }); + }); + + describe('test getSearchResults', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/search?query=harum') + .reply(200, { data: {} }); + return sdk.search + .getSearchResults('harum') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/search?query=atque') + .reply(200, { data: {} }); + return expect(async () => await sdk.search.getSearchResults()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/search?query=qui') + .reply(404, { data: {} }); + return expect(async () => await sdk.search.getSearchResults('qui')).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/security/Security.test.ts b/test/services/security/Security.test.ts new file mode 100644 index 00000000..86189827 --- /dev/null +++ b/test/services/security/Security.test.ts @@ -0,0 +1,77 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { SecurityService } from '../../../src/services/security/Security'; + +describe('test SecurityService object', () => { + it('should be an object', () => { + expect(typeof SecurityService).toBe('function'); + }); +}); + +describe('test Security', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getTransientToken', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/security/token?type_=consectetur&scope=sed') + .reply(200, { data: {} }); + return sdk.security + .getTransientToken('consectetur', 'sed') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/security/token?type_=laborum&scope=est') + .reply(200, { data: {} }); + return expect(async () => await sdk.security.getTransientToken()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/security/token?type_=debitis&scope=tempora') + .reply(404, { data: {} }); + return expect( + async () => await sdk.security.getTransientToken('debitis', 'tempora'), + ).rejects.toThrow(); + }); + }); + + describe('test getSourceConnectionInformation', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/security/resources?source=quod') + .reply(200, { data: {} }); + return sdk.security + .getSourceConnectionInformation('quod') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/security/resources?source=repudiandae') + .reply(200, { data: {} }); + return expect( + async () => await sdk.security.getSourceConnectionInformation(), + ).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/security/resources?source=similique') + .reply(404, { data: {} }); + return expect( + async () => await sdk.security.getSourceConnectionInformation('similique'), + ).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/server/Server.test.ts b/test/services/server/Server.test.ts new file mode 100644 index 00000000..b84797b7 --- /dev/null +++ b/test/services/server/Server.test.ts @@ -0,0 +1,111 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { ServerService } from '../../../src/services/server/Server'; + +describe('test ServerService object', () => { + it('should be an object', () => { + expect(typeof ServerService).toBe('function'); + }); +}); + +describe('test Server', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getServerCapabilities', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/').reply(200, { data: {} }); + return sdk.server + .getServerCapabilities() + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getServerPreferences', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/:/prefs').reply(200, { data: {} }); + return sdk.server + .getServerPreferences() + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getAvailableClients', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/clients').reply(200, { data: {} }); + return sdk.server + .getAvailableClients() + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getDevices', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/devices').reply(200, { data: {} }); + return sdk.server.getDevices().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getServerIdentity', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/identity').reply(200, { data: {} }); + return sdk.server.getServerIdentity().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getMyPlexAccount', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/myplex/account') + .reply(200, { data: {} }); + return sdk.server.getMyPlexAccount().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getResizedPhoto', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/photo/:/transcode?width=9234993610&height=6&opacity=1&blur=5&minSize=6&upscale=4&url=ab', + ) + .reply(200, { data: {} }); + return sdk.server + .getResizedPhoto(9234993610, 6, 1, 5, 6, 4, 'ab') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/photo/:/transcode?width=8995296218&height=6&opacity=5&blur=4&minSize=4&upscale=3&url=perferendis', + ) + .reply(200, { data: {} }); + return expect(async () => await sdk.server.getResizedPhoto()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/photo/:/transcode?width=7861841002&height=3&opacity=9&blur=3&minSize=9&upscale=9&url=esse', + ) + .reply(404, { data: {} }); + return expect( + async () => await sdk.server.getResizedPhoto(7861841002, 3, 9, 3, 9, 9, 'esse'), + ).rejects.toThrow(); + }); + }); + + describe('test getServerList', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}').get('/servers').reply(200, { data: {} }); + return sdk.server.getServerList().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); +}); diff --git a/test/services/sessions/Sessions.test.ts b/test/services/sessions/Sessions.test.ts new file mode 100644 index 00000000..c7b10c98 --- /dev/null +++ b/test/services/sessions/Sessions.test.ts @@ -0,0 +1,77 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { SessionsService } from '../../../src/services/sessions/Sessions'; + +describe('test SessionsService object', () => { + it('should be an object', () => { + expect(typeof SessionsService).toBe('function'); + }); +}); + +describe('test Sessions', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getSessions', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/status/sessions') + .reply(200, { data: {} }); + return sdk.sessions.getSessions().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getSessionHistory', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/status/sessions/history/all') + .reply(200, { data: {} }); + return sdk.sessions + .getSessionHistory() + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test getTranscodeSessions', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/transcode/sessions') + .reply(200, { data: {} }); + return sdk.sessions + .getTranscodeSessions() + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test stopTranscodeSession', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/transcode/sessions/tenetur') + .reply(200, { data: {} }); + return sdk.sessions + .stopTranscodeSession('tenetur') + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/transcode/sessions/reprehenderit') + .reply(200, { data: {} }); + return expect(async () => await sdk.sessions.stopTranscodeSession()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .delete('/transcode/sessions/omnis') + .reply(404, { data: {} }); + return expect(async () => await sdk.sessions.stopTranscodeSession('omnis')).rejects.toThrow(); + }); + }); +}); diff --git a/test/services/updater/Updater.test.ts b/test/services/updater/Updater.test.ts new file mode 100644 index 00000000..7e2e35d4 --- /dev/null +++ b/test/services/updater/Updater.test.ts @@ -0,0 +1,52 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { UpdaterService } from '../../../src/services/updater/Updater'; + +describe('test UpdaterService object', () => { + it('should be an object', () => { + expect(typeof UpdaterService).toBe('function'); + }); +}); + +describe('test Updater', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test getUpdateStatus', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get('/updater/status') + .reply(200, { data: {} }); + return sdk.updater.getUpdateStatus().then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test checkForUpdates', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .put('/updater/check?download=foo') + .reply(200, { data: {} }); + return sdk.updater + .checkForUpdates({ download: 'foo' }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); + + describe('test applyUpdates', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .put('/updater/apply?tonight=foo&skip=foo') + .reply(200, { data: {} }); + return sdk.updater + .applyUpdates({ tonight: 'foo', skip: 'foo' }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + }); +}); diff --git a/test/services/video/Video.test.ts b/test/services/video/Video.test.ts new file mode 100644 index 00000000..8fee823e --- /dev/null +++ b/test/services/video/Video.test.ts @@ -0,0 +1,124 @@ +import nock from 'nock'; + +import { PlexSDK } from '../../../src'; + +import { VideoService } from '../../../src/services/video/Video'; + +describe('test VideoService object', () => { + it('should be an object', () => { + expect(typeof VideoService).toBe('function'); + }); +}); + +describe('test Video', () => { + let sdk: any; + + beforeEach(() => { + sdk = new PlexSDK({}); + + nock.cleanAll(); + }); + + describe('test startUniversalTranscode', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/video/:/transcode/universal/start.mpd?hasMDE=7&path=ut&mediaIndex=4&partIndex=4&protocol=quam&fastSeek=9&directPlay=5&directStream=1&subtitleSize=3&subtites=unde&audioBoost=3&location=amet&mediaBufferSize=4&session=vero&addDebugOverlay=8&autoAdjustQuality=2', + ) + .reply(200, { data: {} }); + return sdk.video + .startUniversalTranscode(7, 'ut', 4, 4, 'quam', { + fastSeek: 9, + directPlay: 5, + directStream: 1, + subtitleSize: 3, + subtites: 'unde', + audioBoost: 3, + location: 'amet', + mediaBufferSize: 4, + session: 'vero', + addDebugOverlay: 8, + autoAdjustQuality: 2, + }) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/video/:/transcode/universal/start.mpd?hasMDE=5&path=id&mediaIndex=5&partIndex=3&protocol=commodi&fastSeek=4&directPlay=1&directStream=9&subtitleSize=4&subtites=amet&audioBoost=1&location=eaque&mediaBufferSize=4&session=veritatis&addDebugOverlay=7&autoAdjustQuality=5', + ) + .reply(200, { data: {} }); + return expect(async () => await sdk.video.startUniversalTranscode()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/video/:/transcode/universal/start.mpd?hasMDE=2&path=cum&mediaIndex=4&partIndex=9&protocol=odit&fastSeek=6&directPlay=7&directStream=3&subtitleSize=7&subtites=amet&audioBoost=5&location=nam&mediaBufferSize=2&session=libero&addDebugOverlay=8&autoAdjustQuality=5', + ) + .reply(404, { data: {} }); + return expect( + async () => + await sdk.video.startUniversalTranscode(2, 'cum', 4, 9, 'odit', { + fastSeek: 6, + directPlay: 7, + directStream: 3, + subtitleSize: 7, + subtites: 'amet', + audioBoost: 5, + location: 'nam', + mediaBufferSize: 2, + session: 'libero', + addDebugOverlay: 8, + autoAdjustQuality: 5, + }), + ).rejects.toThrow(); + }); + }); + + describe('test getTimeline', () => { + test('test api call', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/:/timeline?ratingKey=6&key=ullam&state=sunt&hasMDE=6&time=3&duration=8&context=quasiillovoluptatempraesentiumquae&playQueueItemID=8&playBackTime=2&row=7', + ) + .reply(200, { data: {} }); + return sdk.video + .getTimeline(6, 'ullam', 'sunt', 6, 3, 8, 'quasi illo voluptatem praesentium quae', 8, 2, 7) + .then((r: any) => expect(r.data).toEqual({ data: {} })); + }); + + test('test will throw error if required fields missing', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/:/timeline?ratingKey=3&key=debitis&state=voluptatem&hasMDE=5&time=7&duration=8&context=excepturiquosuscipitundeprovident&playQueueItemID=4&playBackTime=6&row=2', + ) + .reply(200, { data: {} }); + return expect(async () => await sdk.video.getTimeline()).rejects.toThrow(); + }); + + test('test will throw error on a non-200 response', () => { + const scope = nock('{protocol}://{ip}:{port}') + .get( + '/:/timeline?ratingKey=3&key=veniam&state=ad&hasMDE=5&time=6&duration=4&context=laboriosameligendiaspernaturfugitnihil&playQueueItemID=8&playBackTime=1&row=1', + ) + .reply(404, { data: {} }); + return expect( + async () => + await sdk.video.getTimeline( + 3, + 'veniam', + 'ad', + 5, + 6, + 4, + 'laboriosam eligendi aspernatur fugit nihil', + 8, + 1, + 1, + ), + ).rejects.toThrow(); + }); + }); +}); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 00000000..7b398aa3 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./src", "./test" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..651d48fd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": "./src", + "noImplicitAny": true, + "baseUrl": "./", + "declaration": true, + "moduleResolution": "node", + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "declarationMap": true, + "lib": ["ES2021.String", "dom", "esnext"], + "sourceMap": true + }, + "include": [ + "./src/" + ], + "exclude": [ + "src/client/@custom_types" + ] +}