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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@wix/services-manager

v1.0.0

Published

A services manager for managing services used in order to generate SDK packages for Viewer, app settings and dashboard pages.

Readme

Services Manager

A services manager for managing services used in order to generate SDK packages for Viewer, app settings and dashboard pages.

Usage Example

yarn dev

This will start a demo application that uses the services manager to manage services. It includes an iframe and a worker which are running services managers which communicate using the a remote services manager (using Comlink). In the demo application, you can see how the services manager can be used to manage services and how services can be shared between different contexts (iframe, worker, etc), and how the communication is done using the remote services manager. The application logic is demonstrated in the following diagram: config-diagram.png

All the code for the usage example is located in the test-data folder.

Design Document

The design document for the services manager can be found here

API

ServiceAPI and defineService

ServiceAPI<T> and defineService<TAPI, TConfig> are two utility types and functions that are used to define and extract the API of a service. The ServiceAPI<T> type extracts the API interface from a given ServiceDefinition. It represents the actual methods and properties that a service exposes.

Example:

import { defineService, ReadableSignal } from '@wix/services-manager';

interface MyServiceAPI {
  methodA: (input: string) => Promise<number>;
  methodB: () => Promise<string>;
  signals: {
    mySignal: ReadableSignal<number>;
  };
}

const myServiceDefinition = defineService<MyServiceAPI, {}>('myService');

The defineService function creates a ServiceDefinition. A ServiceDefinition is a unique identifier (string) that also carries type information about the service's API and configuration.

ServiceFactory

The ServiceFactory is a function that creates a service instance. It is used to create a service instance when the service is requested.

import { myServiceDefinition } from './my-service-definition';
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';

const myServiceFactory: ServiceFactory<MyServiceAPI, {}> = async ({ config, getService }) => {
  const signalsService = getService(SignalsServiceDefinition);
  const mySignal = signalsService.signal(0);
  return {
    methodA: async (input: string) => {
      return input.length;
    },
    methodB: async () => {
      return 'hello';
    },
    signals: {
      mySignal,
    },
  };
};

The factory function receives an object with the service's configuration and a getService function that can be used to get other services.

ServiceManager

The ServiceManager class is the main class that is used to manage services. It is responsible for creating, registering, and managing services.

Example

import { createServicesManager, createServicesMap } from '@wix/services-manager';
import { myServiceDefinition, myServiceFactory } from './my-service-definition';

const manager = createServicesManager(
  createServicesMap().addService(myServiceDefinition, myServiceFactory, {}),
);

The createServicesManager function creates a new ServiceManager instance with the given services map. It can also accept a second argument, SignalsRegistry, which is used to manage signals. In most cases, you can use the default SignalsRegistry implementation (create new), but in case for some reason you have more than one services manager in the same frame, you can pass the same SignalsRegistry instance to both of them.

Methods

  • getService<T>(serviceDefinition: ServiceDefinition<T>): T - Returns the service instance for the given service definition, uses the ServiceFactory in order to create the service if it is not already created.
  • hasService(serviceDefinition: ServiceDefinition): boolean - Returns true if the service is registered.
  • addService<T, TConfig>(serviceDefinition: ServiceDefinition<T>, factory: ServiceFactory<T, TConfig>, config: TConfig): void - Registers a new service.
  • addServices(servicesRegistrar: ServicesRegistrar): void - Registers and initializes multiple services at once from a ServicesRegistrar (as created by createServicesMap).
  • getSignalsRegistry(): SignalsRegistry - Returns the signals registry instance.

Core Services

Core services are services that are built-in and are always available in the services manager. This includes the SignalsService which is used to manage signals.

Signals

Signals are a way to share state between services. They are used to notify other services about changes or events. This is using preact-signals under the hood. This allows an easy pub/sub mechanism. Signals are created using a dedicated core service - SignalsService.

Example

import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';

const signalsService = manager.getService(SignalsServiceDefinition);
const mySignal = signalsService.signal(0);
const myComputedSignal = signalsService.computed(signalsService.computed(() => mySignal.get() + 3));

mySignal.subscribe((value) => {
  console.log('mySignal value changed:', value);
});
myComputedSignal.subscribe((value) => {
  console.log('myComputedSignal value changed:', value);
});

mySignal.set(5);
// Output:
// mySignal value changed: 5
// myComputedSignal value changed: 8

The signal method creates a new read/write signal with the given initial value. The computed method creates a new read-only signal that is computed from other signals.

Remote Services Manager

The RemoteServicesManager class is used to communicate with services in a different context (iframe, worker, etc). It is used to create a proxy for a remote services manager and to create a proxy for a remote service. The remote manager is wrapping a ServiceManager instance and used in order to connect to a remote services manager.

Example

import { connectRemoteWorker, createRemoteServicesManager } from '@wix/services-manager/remote-helpers';
import { createServicesManager, createServicesMap } from '@wix/services-manager';
import { myServiceDefinition, myServiceFactory } from './my-service-definition';

// in worker.js
const managerInWorker = createServicesManager(createServicesMap());

// create remote mamanger and wait for connection (trigger a ready message)
const remoteManager = createRemoteServicesManager({
  servicesManager: managerInWorker,
  messageFrame: self,
});

remoteManager.awaitConnectionFromMain().then(() => {
  console.log('Connected to main');
  // the service was added as a proxied service during the connection
  const myService = managerInWorker.getService(myServiceDefinition);
  // all proxied methods are async
  myService.methodA('hello').then((result) => {
    console.log('methodA result:', result);
  });
});

// in main.js
const manager = createServicesManager(
  createServicesMap().addService(myServiceDefinition, myServiceFactory, {}),
);

const worker = new Worker('worker.js');
const remoteWorkerManager = await connectRemoteWorker(manager, worker, {
  // wait until the worker sent a message that it is ready
  awaitInitialConnection: true,
  // define the services that are exposed to the worker and should be proxied
  remoteServices: [
    myServiceDefinition,
  ]
});

API

  • createRemoteServicesManager - Creates a new RemoteServicesManager instance.
    • servicesManager: The ServiceManager instance that would be wrapped by the remote manager.
    • messageFrame: The frame that the remote manager is communicating with (self in most cases).
  • connectRemoteWorker/connectRemoteIframe - Connects to a remote worker/iframe and creates a proxy for the remote services manager.
    • servicesManager: The ServiceManager instance that would be wrapped by the remote manager.
    • worker (for connectRemoteWorker) /iframe (for connectRemoteIframe): The worker instance/iframe that the remote manager is communicating with.
    • options (optional): Options for the connection.
      • awaitInitialConnection(optional): If true, the function will wait for the worker/iframe to send a ready message before resolving.
      • retryOptions (optional): { timeout?: number; interval?: number } - Options for retrying the connection.
        • timeout (optional): The timeout for the connection.
        • interval (optional): The interval between retries - if the interval is longer than the timeout, the connection will only be attempted once (default).
      • remoteServices (optional): An array of service definitions that should be proxied.

The Remote Services Manager handshake

When creating a remote manager instance in a worker or iframe, it immediately sends a message to the main thread to establish a connection. The main thread uses connectRemoteWorker or connectRemoteIframe to connect to the remote manager, it can either attempt connecting immediately (eagerly with retries) or wait for the worker/iframe to send a ready message. As soon as the connection is established, the main services manager sends a message to the remote manager with the services that should be proxied, and which signals should be replicated and synced. The handshake sequence is demonstrated in the following diagram: handshake.png The signals' replication and syncing is demonstrated in the following diagram: signals-replication.png

Security Considerations

The solution does provide isolation of the services running in the worker/iframe from the main thread, but it does not provide full isolation. The services manager can determine if an app can access a service, but it cannot prevent app code from trying to access global objects and manipulate code running in the same context. When using the remote services manager, it is possible to control what is being synchronized and proxied, so if a real isolation is needed, it is recommended to only proxy the services that are needed and to avoid exposing global objects.

Signals

Signals are replicated and synced between the main thread and the worker/iframe, ReadOnlySignals are only aimed to define a signal which is computed from other signals and not to define access control, so a service cannot prevent another service running in the same context from updating its signals. Therefore, it is recommended to avoid using signals for sensitive data, and to use them only for state management and notifications.

Running Services in a Worker

In order to create a proper sandboxed environment, it is recommended to run untrusted code in an iFrame or a worker. The services manager allows such approach. The services manager can be used to run services in a worker. This is done by creating a services manager in the worker which runs actual service instances and connecting to it from the main thread. This is useful for running heavy services in a worker in order to avoid blocking the main thread, or when the services are provided by a 3rd party and you want to run them in a sandboxed environment. This is achieved using the RemoteServicesManager and connectRemoteWorker + createServiceProxy functions. Usage examples:

import { connectRemoteWorker, createRemoteServicesManager, createServiceProxy } from '@wix/services-manager/remote-helpers';
import { createServicesManager, createServicesMap } from '@wix/services-manager';
import { myServiceDefinition, myServiceFactory } from './my-service-definition';

// in worker.js
const managerInWorker = createServicesManager(
  // the service factory is in the worker
  createServicesMap().addService(myServiceDefinition, myServiceFactory, {}),
);

// create remote mamanger and wait for connection (trigger a ready message)
const remoteManager = createRemoteServicesManager({
  servicesManager: managerInWorker,
  messageFrame: self,
});

remoteManager.awaitConnectionFromMain().then(() => {
  console.log('Connected to main');
});

// in main.js
const manager = createServicesManager(createServicesMap());

const worker = new Worker('worker.js');
const remoteWorkerManager = await connectRemoteWorker(manager, worker, {
  // wait until the worker sent a message that it is ready
  awaitInitialConnection: true,
  // no services are proxied in worker
  remoteServices: []
});

manager.addService(myServiceDefinition, () => createServiceProxy(
  // The service definition is the same as in the worker
  myServiceDefinition,
  // The remote manager connected to the worker which is running the service
  remoteWorkerManager,
  // The `SignalsRegistry` used for the signals of the service to proxy
  manager.getSignalsRegistry(),
));

const myService = manager.getService(myServiceDefinition);
// all proxied methods are async
myService.methodA('hello').then((result) => {
  console.log('methodA result:', result);
});

Since a service can be used in multiple contexts (iframe, worker, etc), the service manager can be used to manage services in different contexts and to communicate between them using the remote services manager. This also allows services in one worker to consume services from another worker (via the services manager running in the main thread). proxy-to-proxy-diagram.png In the diagram above, app 2 is running in a worker and it exposes a service which is proxied in the main thread. App 1 is running in a worker and uses its parent (the services manager in the main thread) to get the service from app 2. From its point of view, it is using the service directly, but the service is actually running in a different worker.