@sanity/state-router
Advanced tools
Comparing version 2.21.11-reference-updates.62 to 2.21.12-purple-unicorn.1300
{ | ||
"name": "@sanity/state-router", | ||
"version": "2.21.11-reference-updates.62+f98497b413", | ||
"version": "2.21.12-purple-unicorn.1300+abdd014728", | ||
"description": "A path pattern => state object bidirectional mapper", | ||
"main": "./lib/_exports/index.js", | ||
"types": "index.d.ts", | ||
"//": "the typesVersion config below is a workaround for TypeScript's lack of support for package exports", | ||
"typesVersions": { | ||
"*": { | ||
"*": [ | ||
"dist/dts/_exports/*" | ||
] | ||
"source": "./src/index.ts", | ||
"main": "./lib/index.cjs", | ||
"module": "./lib/index.js", | ||
"exports": { | ||
".": { | ||
"source": "./src/index.ts", | ||
"require": "./lib/index.cjs", | ||
"default": "./lib/index.js" | ||
} | ||
}, | ||
"types": "./lib/dts/src/index.d.ts", | ||
"files": [ | ||
"lib", | ||
"src" | ||
], | ||
"scripts": { | ||
"start": "cd demo && vite .", | ||
"benchmark": "run-s build benchmark:run", | ||
"benchmark:run": "node --prof --logfile=benchmarks.log perf/benchmark.js", | ||
"build": "../../../bin/pkg-utils bundle --tsconfig tsconfig.lib.json", | ||
"clean": "rimraf *.log lib", | ||
"dev": "cd demo && vite", | ||
"test": "jest", | ||
"compile:watch": "babel --watch --out-dir lib/ src/", | ||
"benchmark": "npm run compile && node --prof --logfile=benchmarks.log perf/benchmark.js", | ||
"clean": "rimraf coverage dist lib" | ||
"test:coverage": "jest --coverage", | ||
"prebuild": "yarn clean", | ||
"watch": "../../../bin/pkg-utils bundle --tsconfig tsconfig.lib.json --watch" | ||
}, | ||
@@ -34,15 +43,15 @@ "keywords": [ | ||
"debug": "^3.2.7", | ||
"lodash": "^4.17.15", | ||
"nano-pubsub": "^2.0.1" | ||
"lodash": "^4.17.21" | ||
}, | ||
"devDependencies": { | ||
"@vitejs/plugin-react-refresh": "^1.3.6", | ||
"@sanity/ui": "^0.37.5", | ||
"@types/object-inspect": "^1.8.1", | ||
"@vitejs/plugin-react": "^1.1.3", | ||
"history": "^4.6.3", | ||
"object-inspect": "^1.6.0", | ||
"prop-types": "^15.6.0", | ||
"react": "17.0.1", | ||
"react-dom": "17.0.1", | ||
"rimraf": "^2.7.1", | ||
"react": "17.0.2", | ||
"react-dom": "17.0.2", | ||
"rimraf": "^3.0.2", | ||
"ts-node": "^9.1.1", | ||
"vite": "^2.4.4" | ||
"vite": "^2.7.2" | ||
}, | ||
@@ -62,3 +71,3 @@ "peerDependencies": { | ||
"homepage": "https://www.sanity.io/", | ||
"gitHead": "f98497b413e20f06fcca0923f540b8ae2c8ae564" | ||
"gitHead": "abdd014728981dd514951705252054db61bcd3f4" | ||
} |
@@ -1,3 +0,7 @@ | ||
## @sanity/state-router | ||
# `@sanity/state-router` | ||
```sh | ||
npm install @sanity/state-router | ||
``` | ||
## Features | ||
@@ -14,6 +18,6 @@ Based on a routing schema: | ||
const router = route('/', [ | ||
route('/products/:productId'), | ||
route('/users/:userId'), | ||
route('/:page'), | ||
const router = route.create('/', [ | ||
route.create('/products/:productId'), | ||
route.create('/users/:userId'), | ||
route.create('/:page'), | ||
]) | ||
@@ -51,6 +55,6 @@ | ||
import {route} from '@sanity/state-router' | ||
import {RouterProvider, withRouter} from '@sanity/state-router/components' | ||
import {RouterProvider, withRouter} from '@sanity/state-router' | ||
const router = route('/', [ | ||
route('/bikes/:bikeId') | ||
const router = route.create('/', [ | ||
route.create('/bikes/:bikeId') | ||
]) | ||
@@ -83,5 +87,6 @@ | ||
<RouterProvider | ||
router={router} | ||
onNavigate={handleNavigate} | ||
state={router.decode(location.pathname)}> | ||
router={router} | ||
onNavigate={handleNavigate} | ||
state={router.decode(location.pathname)} | ||
> | ||
<App /> | ||
@@ -97,3 +102,3 @@ </RouterProvider> | ||
- `route(path : string, ?options : Options, ?children : ) : Router` | ||
- `route.create(path : string, ?options : Options, ?children : ) : Router` | ||
- `route.scope(name : string, path : string, ?options : Options, ?children : ) : Router` | ||
@@ -122,3 +127,3 @@ - `Router`: | ||
- `children` can be either another router returned from another `route()-call`, an array of routers or a function that gets passed the matched parameters, and conditionally returns child routes | ||
- `children` can be either another router returned from another `route.create()-call`, an array of routers or a function that gets passed the matched parameters, and conditionally returns child routes | ||
@@ -130,5 +135,5 @@ ## Limitations | ||
```js | ||
const router = route('/', [ | ||
route('/about'), | ||
route('/contact') | ||
const router = route.create('/', [ | ||
route.create('/about'), | ||
route.create('/contact') | ||
]) | ||
@@ -139,3 +144,3 @@ ``` | ||
```js | ||
const router = route('/', route('/:page')) | ||
const router = route.create('/', route.create('/:page')) | ||
``` | ||
@@ -162,3 +167,3 @@ | ||
const router = route('/some/:section/:settings', { | ||
const router = route.create('/some/:section/:settings', { | ||
transform: { | ||
@@ -170,3 +175,3 @@ settings: { | ||
} | ||
}, route('/other/:page')) | ||
}, route.create('/other/:page')) | ||
``` | ||
@@ -210,9 +215,9 @@ This call... | ||
name: 'pokemon', | ||
router: route('/:section', route('/:pokemonName')) | ||
router: route.create('/:section', route.create('/:pokemonName')) | ||
} | ||
} | ||
const router = route('/', [ | ||
route('/users/:username'), | ||
route('/apps/:appName', params => { | ||
const router = route.create('/', [ | ||
route.create('/users/:username'), | ||
route.create('/apps/:appName', params => { | ||
const app = findAppByName(params.appName) | ||
@@ -257,4 +262,4 @@ return app && route.scope(app.name, '/', app.router) | ||
``` | ||
const router = route('/', [ | ||
route('/users/:username'), | ||
const router = route.create('/', [ | ||
route.create('/users/:username'), | ||
route.intents('/intents') // <-- sets up intent routes at the /intents base path | ||
@@ -280,3 +285,3 @@ ]) | ||
```js | ||
const router = route('/pages/:page') | ||
const router = route.create('/pages/:page') | ||
@@ -292,5 +297,5 @@ router.isNotFound('/some/invalid/path') | ||
```js | ||
const router = route('/some/basepath', [ | ||
route('/:foo'), | ||
route('/:bar') | ||
const router = route.create('/some/basepath', [ | ||
route.create('/:foo'), | ||
route.create('/:bar') | ||
]) | ||
@@ -297,0 +302,0 @@ ``` |
@@ -9,4 +9,6 @@ import {Route, Segment} from './types' | ||
} | ||
if (segment.startsWith(':')) { | ||
const paramName = segment.substring(1) | ||
if (!VALID_PARAM_SEGMENT.test(paramName)) { | ||
@@ -21,8 +23,10 @@ const addendum = segment.includes('*') | ||
} | ||
return {type: 'param', name: paramName} | ||
} | ||
return {type: 'dir', name: segment} | ||
} | ||
export default function parseRoute(route: string): Route { | ||
export function parseRoute(route: string): Route { | ||
const [pathname] = route.split('?') | ||
@@ -29,0 +33,0 @@ |
@@ -1,10 +0,14 @@ | ||
import {Node, MatchResult} from './types' | ||
import findMatchingNodes from './findMatchingNodes' | ||
import {flatten} from 'lodash' | ||
import {findMatchingRoutes} from './findMatchingRoutes' | ||
import {RouterNode, MatchResult} from './types' | ||
import {debug} from './utils/debug' | ||
export default function resolvePathFromState(node: Node, state: Record<string, unknown>): string { | ||
/** | ||
* @public | ||
*/ | ||
export function resolvePathFromState(node: RouterNode, state: Record<string, unknown>): string { | ||
debug('Resolving path from state %o', state) | ||
const match: MatchResult = findMatchingNodes(node, state) | ||
const match: MatchResult = findMatchingRoutes(node, state) | ||
if (match.remaining.length > 0) { | ||
@@ -24,2 +28,3 @@ const remaining = match.remaining | ||
let scopedState: Record<string, unknown> = state | ||
const relative = flatten( | ||
@@ -30,2 +35,3 @@ match.nodes.map((matchNode) => { | ||
} | ||
return matchNode.route.segments.map((segment) => { | ||
@@ -35,4 +41,8 @@ if (segment.type === 'dir') { | ||
} | ||
const transform = matchNode.transform && matchNode.transform[segment.name] | ||
return transform ? transform.toPath(scopedState[segment.name]) : scopedState[segment.name] | ||
return transform | ||
? transform.toPath(scopedState[segment.name] as any) | ||
: scopedState[segment.name] | ||
}) | ||
@@ -39,0 +49,0 @@ }) |
@@ -1,13 +0,9 @@ | ||
import {Node} from './types' | ||
import {RouterNode} from './types' | ||
import {debug} from './utils/debug' | ||
import arrayify from './utils/arrayify' | ||
import {arrayify} from './utils/arrayify' | ||
function matchPath( | ||
node: Node, | ||
path: string | ||
): { | ||
[key: string]: string | ||
} | null { | ||
function matchPath(node: RouterNode, path: string): Record<string, string> | null { | ||
const parts = path.split('/').filter(Boolean) | ||
const segmentsLength = node.route.segments.length | ||
if (parts.length < segmentsLength) { | ||
@@ -17,3 +13,3 @@ return null | ||
const state = {} | ||
const state: Record<string, unknown> = {} | ||
const isMatching = node.route.segments.every((segment, i) => { | ||
@@ -23,4 +19,7 @@ if (segment.type === 'dir') { | ||
} | ||
const transform = node.transform && node.transform[segment.name] | ||
state[segment.name] = transform ? transform.toState(parts[i]) : parts[i] | ||
return true | ||
@@ -34,7 +33,10 @@ }) | ||
const rest = parts.slice(segmentsLength) | ||
let childState: { | ||
[key: string]: string | ||
} | null = null | ||
const children = | ||
typeof node.children === 'function' ? arrayify(node.children(state)) : node.children | ||
children.some((childNode) => { | ||
@@ -44,4 +46,10 @@ // console.log('----childNode') | ||
// console.log('----childNode') | ||
childState = matchPath(childNode, rest.join('/')) | ||
return childState | ||
if (childNode) { | ||
childState = matchPath(childNode, rest.join('/')) | ||
return childState | ||
} | ||
return undefined | ||
}) | ||
@@ -54,6 +62,10 @@ | ||
const mergedState = {...state, ...(childState || {})} | ||
return node.scope ? {[node.scope]: mergedState} : mergedState | ||
} | ||
export default function resolveStateFromPath(node: Node, path: string): Record<string, any> | null { | ||
/** | ||
* @public | ||
*/ | ||
export function resolveStateFromPath(node: RouterNode, path: string): Record<string, any> | null { | ||
debug('resolving state from path %s', path) | ||
@@ -64,3 +76,4 @@ | ||
debug('resolved: %o', pathMatch || null) | ||
return pathMatch || null | ||
} |
@@ -0,10 +1,12 @@ | ||
import {parseRoute} from './parseRoute' | ||
import {resolveStateFromPath} from './resolveStateFromPath' | ||
import {resolvePathFromState} from './resolvePathFromState' | ||
import {Transform, Router, RouteChildren} from './types' | ||
import parseRoute from './parseRoute' | ||
import resolveStateFromPath from './resolveStateFromPath' | ||
import resolvePathFromState from './resolvePathFromState' | ||
import {decodeJsonParams, encodeJsonParams} from './utils/jsonParamsEncoding' | ||
import {decodeParams, encodeParams} from './utils/paramsEncoding' | ||
import {decodeJsonParams, encodeJsonParams} from './utils/jsonParamsEncoding' | ||
type NodeOptions = { | ||
/** | ||
* @public | ||
*/ | ||
export type NodeOptions = { | ||
path?: string | ||
@@ -38,2 +40,3 @@ children?: RouteChildren | ||
} | ||
if ( | ||
@@ -46,10 +49,25 @@ Array.isArray(childrenOrOpts) || | ||
} | ||
if (children) { | ||
return {path, ...childrenOrOpts, children: normalizeChildren(children)} | ||
} | ||
return {path, ...childrenOrOpts} | ||
} | ||
export default function route( | ||
routeOrOpts: string | NodeOptions, | ||
/** | ||
* @public | ||
*/ | ||
export const route: { | ||
create: ( | ||
routeOrOpts: NodeOptions | string, | ||
childrenOrOpts?: NodeOptions | RouteChildren | null, | ||
children?: Router | RouteChildren | ||
) => Router | ||
intents: (base: string) => Router | ||
scope: (scopeName: string, ...rest: any[]) => Router | ||
} = {create: createRoute, scope: routeScope, intents: routeIntents} | ||
function createRoute( | ||
routeOrOpts: NodeOptions | string, | ||
childrenOrOpts?: NodeOptions | RouteChildren | null, | ||
@@ -61,3 +79,3 @@ children?: Router | RouteChildren | ||
route.scope = function scope(scopeName: string, ...rest: any[]): Router { | ||
function routeScope(scopeName: string, ...rest: any[]): Router { | ||
const options = normalizeArgs(...rest) | ||
@@ -71,10 +89,11 @@ | ||
function normalize(...paths) { | ||
return paths.reduce((acc, path) => acc.concat(path.split('/')), []).filter(Boolean) | ||
function normalize(...paths: string[]) { | ||
return paths.reduce<string[]>((acc, path) => acc.concat(path.split('/')), []).filter(Boolean) | ||
} | ||
route.intents = function intents(base) { | ||
function routeIntents(base: string): Router { | ||
const basePath = normalize(base).join('/') | ||
return route(`${basePath}/:intent`, [ | ||
route( | ||
return route.create(`${basePath}/:intent`, [ | ||
route.create( | ||
':params', | ||
@@ -90,3 +109,3 @@ { | ||
[ | ||
route(':payload', { | ||
route.create(':payload', { | ||
transform: { | ||
@@ -105,4 +124,6 @@ payload: { | ||
const EMPTY_STATE = {} | ||
function isRoot(pathname: string): boolean { | ||
const parts = pathname.split('/') | ||
for (let i = 0; i < parts.length; i++) { | ||
@@ -113,2 +134,3 @@ if (parts[i]) { | ||
} | ||
return true | ||
@@ -119,5 +141,7 @@ } | ||
const {path, scope, transform, children} = options | ||
if (!path) { | ||
throw new TypeError('Missing path') | ||
} | ||
const parsedRoute = parseRoute(path) | ||
@@ -124,0 +148,0 @@ |
import React from 'react' | ||
import {InternalRouter} from './components/types' | ||
import {RouterContextValue} from './types' | ||
const missingContext = () => { | ||
throw new Error('No router context provider found') | ||
} | ||
export const RouterContext = React.createContext<InternalRouter>({ | ||
channel: {subscribe: missingContext, publish: missingContext}, | ||
getState: missingContext, | ||
navigate: missingContext, | ||
navigateIntent: missingContext, | ||
navigateUrl: missingContext, | ||
resolveIntentLink: missingContext, | ||
resolvePathFromState: missingContext, | ||
}) | ||
/** | ||
* @public | ||
*/ | ||
export const RouterContext = React.createContext<RouterContextValue | null>(null) |
@@ -1,2 +0,5 @@ | ||
export type Segment = { | ||
/** | ||
* @public | ||
*/ | ||
export interface Segment { | ||
name: string | ||
@@ -6,3 +9,6 @@ type: 'dir' | 'param' | ||
export type Transform<T> = { | ||
/** | ||
* @public | ||
*/ | ||
export interface Transform<T> { | ||
toState: (value: string) => T | ||
@@ -12,18 +18,28 @@ toPath: (value: T) => string | ||
export type Route = { | ||
/** | ||
* @public | ||
*/ | ||
export interface Route { | ||
raw: string | ||
segments: Segment[] | ||
transform?: { | ||
[key: string]: Transform<any> | ||
[key: string]: Transform<RouterState> | ||
} | ||
} | ||
// eslint-disable-next-line no-use-before-define | ||
export type RouteChildren = Node[] | ((state: Record<string, unknown>) => Node[]) | ||
/** | ||
* @public | ||
*/ | ||
export type RouteChildren = | ||
| RouterNode[] | ||
| ((state: RouterState) => Router | RouterNode | RouterNode[] | undefined | false) | ||
export type Node = { | ||
/** | ||
* @public | ||
*/ | ||
export interface RouterNode { | ||
route: Route | ||
scope?: string | ||
transform?: { | ||
[key: string]: Transform<any> | ||
[key: string]: Transform<RouterState> | ||
} | ||
@@ -33,6 +49,9 @@ children: RouteChildren | ||
export type Router = Node & { | ||
/** | ||
* @public | ||
*/ | ||
export interface Router extends RouterNode { | ||
_isRoute: boolean | ||
encode: (state: Record<string, unknown>) => string | ||
decode: (path: string) => Record<string, unknown> | null | ||
encode: (state: RouterState) => string | ||
decode: (path: string) => RouterState | null | ||
isNotFound: (path: string) => boolean | ||
@@ -43,6 +62,37 @@ getBasePath: () => string | ||
} | ||
export type MatchResult = { | ||
nodes: Node[] | ||
export interface MatchResult { | ||
nodes: RouterNode[] | ||
missing: string[] | ||
remaining: string[] | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export type NavigateOptions = { | ||
replace?: boolean | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export type IntentParameters = | ||
| Record<string, unknown> | ||
| [Record<string, unknown>, Record<string, unknown>] | ||
/** | ||
* @public | ||
*/ | ||
export type RouterState = Record<string, unknown> | ||
/** | ||
* @public | ||
*/ | ||
export type RouterContextValue = { | ||
resolvePathFromState: (nextState: RouterState) => string | ||
resolveIntentLink: (intentName: string, params?: IntentParameters) => string | ||
navigateUrl: (opts: {path: string; replace?: boolean}) => void | ||
navigate: (nextState: RouterState, options?: NavigateOptions) => void | ||
navigateIntent: (intentName: string, params?: IntentParameters, options?: NavigateOptions) => void | ||
state: RouterState | ||
} |
import {useContext} from 'react' | ||
import {InternalRouter} from './components/types' | ||
import {RouterContext} from './RouterContext' | ||
import {RouterContextValue} from './types' | ||
export function useRouter(): InternalRouter { | ||
return useContext(RouterContext) | ||
/** | ||
* @public | ||
*/ | ||
export function useRouter(): RouterContextValue { | ||
const router = useContext(RouterContext) | ||
if (!router) { | ||
throw new Error('Router: missing context value') | ||
} | ||
return router | ||
} |
import {identity} from 'lodash' | ||
import {useContext, useEffect, useState} from 'react' | ||
import {RouterState} from './components/types' | ||
import {RouterContext} from './RouterContext' | ||
import {useEffect, useState} from 'react' | ||
import {RouterState} from './types' | ||
import {useRouter} from './useRouter' | ||
/** | ||
* @public | ||
*/ | ||
export function useRouterState<R = RouterState>(selector: (routerState: RouterState) => R): R | ||
/** | ||
* @public | ||
*/ | ||
export function useRouterState(): RouterState | ||
/** | ||
* @public | ||
*/ | ||
export function useRouterState( | ||
selector: (routerState: RouterState) => unknown = identity | ||
): unknown { | ||
const {channel, getState} = useContext(RouterContext) | ||
const [selectedState, setState] = useState(() => selector(getState())) | ||
const {state} = useRouter() | ||
const [selectedState, setState] = useState(() => selector(state)) | ||
// reset the state when the `selector` prop changes | ||
useEffect(() => setState(selector(getState())), [selector, getState]) | ||
useEffect(() => setState(selector(state)), [selector, state]) | ||
// update the state via a subscription | ||
useEffect(() => { | ||
// prevents "Can't perform a React state update on an unmounted component." | ||
const mounted = {current: true} | ||
// useEffect(() => { | ||
// // prevents "Can't perform a React state update on an unmounted component." | ||
// const mounted = {current: true} | ||
const unsubscribe = channel.subscribe(() => { | ||
if (mounted.current) { | ||
setState(selector(getState())) | ||
} | ||
}) | ||
// const unsubscribe = channel.subscribe(() => { | ||
// if (mounted.current) { | ||
// setState(selector(state)) | ||
// } | ||
// }) | ||
return () => { | ||
mounted.current = false | ||
unsubscribe() | ||
} | ||
}, [channel, selector, getState]) | ||
// return () => { | ||
// mounted.current = false | ||
// unsubscribe() | ||
// } | ||
// }, [channel, selector, getState]) | ||
return selectedState | ||
} |
@@ -1,6 +0,7 @@ | ||
export default function arrayify<T>(val: Array<T> | T): Array<T> { | ||
export function arrayify<T>(val: Array<T> | T): Array<T> { | ||
if (Array.isArray(val)) { | ||
return val | ||
} | ||
return typeof val === 'undefined' ? [] : [val] | ||
return val ? [val] : [] | ||
} |
const _hasOWn = Object.prototype.hasOwnProperty | ||
export default _hasOWn.call.bind(_hasOWn) | ||
export const hasOwn = _hasOWn.call.bind(_hasOWn) |
@@ -1,4 +0,4 @@ | ||
import hasOwn from './hasOwn' | ||
import {hasOwn} from './hasOwn' | ||
export default function isEmpty(object: Object): boolean { | ||
export function isEmpty(object: Record<string, unknown>): boolean { | ||
for (const key in object) { | ||
@@ -9,3 +9,4 @@ if (hasOwn(object, key)) { | ||
} | ||
return true | ||
} |
@@ -1,3 +0,4 @@ | ||
export function decodeJsonParams(pathsegment = '') { | ||
export function decodeJsonParams(pathsegment = ''): Record<string, unknown> { | ||
const segment = decodeURIComponent(pathsegment) | ||
if (!segment) { | ||
@@ -16,3 +17,2 @@ return {} | ||
} catch (err) { | ||
// eslint-disable-next-line no-console | ||
console.warn('Failed to parse JSON parameters') | ||
@@ -24,4 +24,4 @@ } | ||
export function encodeJsonParams(params) { | ||
return params === null || typeof params === 'undefined' ? '' : btoa(JSON.stringify(params)) | ||
export function encodeJsonParams(params?: Record<string, unknown>): string { | ||
return params ? btoa(JSON.stringify(params)) : '' | ||
} |
export function decodeParams(pathSegment: string): Record<string, string> { | ||
return pathSegment.split(';').reduce<Record<string, string>>((params, pair) => { | ||
const [key, value] = pair.split('=') | ||
params[decodeURIComponent(key)] = decodeURIComponent(value) | ||
return params | ||
@@ -9,6 +11,7 @@ }, {}) | ||
export function encodeParams(params: Record<string, string>): string { | ||
return Object.keys(params) | ||
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) | ||
export function encodeParams(params: Record<string, string | undefined | null>): string { | ||
return Object.entries(params) | ||
.filter(([, value]) => value !== undefined && value !== null) | ||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`) | ||
.join(';') | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
4
313
0
206277
10
82
2473
2
- Removednano-pubsub@^2.0.1
- Removednano-pubsub@2.0.1(transitive)
Updatedlodash@^4.17.21