npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lokalise/harmony

v4.7.3

Published

Archived: Harmony is deprecated and no longer maintained.

Downloads

249

Readme

Archived: Harmony is deprecated and no longer maintained. The repository is read-only and receives no further updates.

Harmony

A temporary shared library designed to house reusable components, such as molecules and organisms, exclusively for the Expert and Flow platforms during the migration period. This library will be retired upon the completion of the migration and the establishment of the unified next-gen platform.

Deprecation Status

  • Final release: 4.7.3 (documentation-only).
  • The npm package is deprecated and will warn on install.
  • Existing consumers should migrate to the platform-specific implementations that now live in the Expert, Flow, or next-gen repositories.

Storybook

https://lokalise.github.io/harmony/?path=/story/app-shell-navigationpanel--default&args=sticky:!true

Features

Authentication

This provides a wretch addon for authentication. It is used to add the authentication token to the request headers, and will handle automatic token refreshes.

To compliment this, there are a number of convenience hooks and functions to help with authentication in both a frontend and backend context.

Basic Frontend Usage

import { HeaderBuilder, JwtAuthHeaderBuilderMiddleware, generateTokenFromClassicSession, refreshExpiredToken, getJwtTokenFromCookie } from '@lokalise/harmony'
import wretch from 'wretch'

// Create a client pointing at the Lokalise Auth API
const authHttpClient = wretch('path/to/auth/provider')

// This is the client that you would use to make requests to your app's API
//  - It assumes that your API is compatible with the shared authentication strategy
export const appHttpClient = wretch('path/to/you/api')
    .errorType('json') // More config as you see fit

const getTeamId = async () => 000 /* Get the team ID from somewhere*/

export const authenticationHeaderBuilder = HeaderBuilder.create()
    .with(
        JwtAuthHeaderBuilderMiddleware({
            // Provide a way to refresh the token - there are utility functions to help with this in the frontend
            refreshToken: refreshExpiredToken(authHttpClient, getTeamId),

            // Setup a source for the current token - there are utility functions to help with this in the frontend
            getCurrentToken: getJwtTokenFromCookie,

            // Additionally, you can promote a classic session to an authenticated session
            // when configuring a client in the context of a classic PHP CSRF session.
            // this `promoteClassicSession` method will automatically promote the session to JWT
            generateNewToken: generateTokenFromClassicSession(authHttpClient, getCsrfTokenFromCookie, getTeamId),

            // Optionally, you can provide a callback to be notified when a new token is issued
            onNewTokenIssued: (token) => {
                // Do something with the new token
            }
        })
    )

const result = await sendByGetRoute(appHttpClient, getTeamUser, {
    headers: await authenticationHeaderBuilder.resolve(),
    pathParams: { teamId, userId }
})

Public API

This provides a set of type safe request routes for the public API. They are designed to be used with the sendByRoute style functions that accept a wretch client and a route object. Additionally, there are a number of convenience hooks and functions to help with public API requests.

Basic Frontend Usage

import { sendByGetRoute } from '@lokalise/frontend-http-client'
import { type TeamUserResponse, getTeamUser } from '@lokalise/harmony';
import { appAuthenticatedHttpClient } from '../path/to/appAuthenticatedHttpClient';

const result: TeamUserResponse = await sendByGetRoute(appAuthenticatedHttpClient, getTeamUser, { pathParams: { teamId, userId } });

Basic Backend Usage

import { sendByGetRoute } from '@lokalise/backend-http-client'
import { type TeamUserResponse, getTeamUser } from '@lokalise/harmony';
import { Client } from 'undici'

const result: TeamUserResponse = async (client: Client) => {
    return sendByGetRoute(client, getTeamUser, {
        pathParams: { teamId, userId },
        headers: { 'authorization': `Bearer token` }
    });
}

Tests

Query Hooks testing

Query hooks tests functionality has been developed due to inconsistencies in data structure from API responses and zod schemas. zod.parse will error if data is invalid and although this solution is not ideal (until we have data layer that is driving schemas for all apps), we need to do it this way.

It is important not to generate fake data but to use some actual payloads from API, as this iss the source of the problem (like date formatting for example).

If your hook is relatively simple, you may not need to add the hook tests as such, but simply add payload parsing test.

In order to test query hooks, utilise @tests/utils/apiHelpers. Setup is already extended with msw server setup, so there's no need to add any logic. Simply use normal it function, within witch you can use mockGetResponse (POST yet to be implemented) like in example below:

import {
    mockGetResponse,
    renderQueryHook
} from "@tests/utils/apiHelpers";

// Inside tests
// Create expected response
const response = {
    user: {
        id: 1,
        name: 'Test User'
    }
}

// Mock the response
mockGetResponse('/me', responseData)

// Use helper method renderQueryHook to utilise custom client and header builder
const { result } = renderQueryHook(({ client, builder}) => useListTeamsQuery(client, builder, {
    queryKey: ['me']
}))

// Create necessary assertions
await waitFor(() => {
    const { data ,  isSuccess} = result.current
    expect(isSuccess).toBeTruthy();
    expect(data).toEqual(responseData);
});

Permissions Feature

The permissions feature provides a flexible and type-safe way to control access to features and actions throughout the application. It leverages context providers, guard components, and hooks to make permission checks declarative and easy to use in your React components.

Usage Examples

Providing Permissions Context

import {
  FeatureFlagActionResolverContextProvider,
  TeamActionResolverContextProvider,
  ProjectActionResolverContextProvider
} from "@lokalise/harmony";

{/* NB :: as these are contexts and will cause everything below to re-render any time the value updates... */}
{/*       we should make sure that the information is provided once and changed as little as possible. */}
{/*       Ideally this is a request when the page loads and then doesn't change until absolutly needed. */}

{/* Provide the FeatureFlag once at the top level of your app */}
<FeatureFlagActionResolverContextProvider enabledFeatureFlags={["homeMarketingBeta"]}>
    
    {/* Team level context should be provided as soon as team context becomes available */}
    {/* NOTE: This might be lower in the DOM tree than feature-flags context*/}
    {/*       the reason they are separate is to allower for checks outside the scope of a team  */}
    <TeamActionResolverContextProvider teamRole="admin">

        {/* Project level context should be provided when projcet information available */}
        {/* NOTE: This is likely going to be lower in the DOM tree than the team context */}
        <ProjectActionResolverContextProvider projectPermissions={["tasks", "upload"]} projectType="marketing">
            {/* ...your app... */}
        </ProjectActionResolverContextProvider>
    </TeamActionResolverContextProvider>
</FeatureFlagActionResolverContextProvider>

Guarding UI with Permission Checks

import { CanPerformActionGuard, CanPerformAllActionsGuard, CanPerformAnyActionGuard } from "@lokalise/harmony";

<CanPerformActionGuard action="accessMarketingProjects">
  <MarketingDashboard />
</CanPerformActionGuard>

<CanPerformAllActionsGuard actions={["accessMarketingProjects", "deleteProjects"]}>
  <DangerZone />
</CanPerformAllActionsGuard>

<CanPerformAnyActionGuard actions={["accessMarketingProjects", "deleteProjects"]}>
  <ProjectDetails />
</CanPerformAnyActionGuard>

Checking Permissions in Logic

import { useCanPerformAction, useCanPerformAllActions, useCanPerformAnyAction } from "@lokalise/harmony";

const canAccess = useCanPerformAction("accessMarketingProjects");
const canDoAll = useCanPerformAllActions("accessMarketingProjects", "deleteProjects");
const canDoAny = useCanPerformAnyAction("accessMarketingProjects", "deleteProjects");

if (canAccess) {
  // show marketing dashboard
}
if (canDoAll) {
  // show danger zone
}
if (canDoAny) {
  // show details or enable actions
}

Contributing: Creating a New Permission Action

To add a new permission action to the system, follow these steps:

  1. Create the Action Resolver

    • Implement a new resolver function in src/features/permissions/action-resolvers/.
    • The resolver should accept an ActionResolverPayload and return a boolean indicating if the action is permitted.
    • Example:
      // src/features/permissions/action-resolvers/canManageWidgets.ts
      import type { ActionResolver } from '../types';
      import { has } from '../utils/has'
           
      export const canManageWidgets: ActionResolver = ({ teamRole, projectPermissions }) => {
        return has(teamRole, 'admin') && has(projectPermissions, 'settings');
      };
  2. Register the Action

    • Add your new resolver to the actionResolvers object in src/features/permissions/action.ts:
      import { canManageWidgets } from './action-resolvers/canManageWidgets';
           
      export const actionResolvers = {
        // ...existing actions...
        canManageWidgets,
      };
  3. Success!

    • Your new action is now available for use throughout the application via the guards, hooks, and context providers.
    • You can now use it like:
      <CanPerformActionGuard action="canManageWidgets">
        <WidgetAdminPanel />
      </CanPerformActionGuard>

For more details, see the documentation in the src/features/permissions directory.