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 🙏

© 2024 – Pkg Stats / Ryan Hefner

revite

v1.18.4

Published

Easy way to implement configurable application bootstrap.

Downloads

147

Readme

Revite

Easy way to implement configurable application bootstrap.

npm latest version node compatibility build status

Contents

Installation

yarn add revite

Add global.d.ts to tsconfig.json. Set the correct path to node_modules relative to the tsconfig.json directory.

{
  "files": ["%node_modules/revite/global.d.ts"]
}

Getting Started

  1. Create services and providers.
  2. Add these providers to the configuration.
  3. Load configuration via Revite
import { revite } from "revite";
import config from "@revite/config";

revite.bootstrap(config);

See Demo Application

Purpose of Revite development

  • Divide application to the separate isolated abstractions.
  • Remove direct dependencies on external packages and environment variables to simplify further support, refactoring, and upgrade.
  • Give the developer a tool to configure application parts.
  • Simplify understanding of file locations in a project.
  • Allow different versions of the application to run on the same codebase.
  • Simplify testing of application parts through mocked services.

Basic Concepts

Contracts

Contracts are the only one way to get the service instance. Abstract classes are used as contracts.

export abstract class NotesServiceContract {
  notes: Service.Notes.Note[] = [];

  abstract get isEmpty(): boolean;

  abstract createNote(note: Service.Notes.Note): Promise<void>;

  abstract deleteNote(noteId: string): Promise<void>;
}
import { revite } from "revite";
import { NotesServiceContract } from "/~/services/notes";

async function getNotes() {
  const notesService = await revite.resolve(NotesServiceContract);

  return notesService.notes;
}

Configurations

  • Each service can contain its configuration in a separate config file or config section in main.ts.
  • Configuration is a set of files:
config
- production
-- main.ts
-- auth.ts
- test
-- main.ts
-- auth.ts
  • main.ts is a required file for the Revite bootstrap and should look like this:
// "~/services/reactivity/versions/vue";
import { ReactivityServiceContract } from "revite";
import { reactive, shallowReactive } from "vue";

export class VueReactivityService extends ReactivityServiceContract {
  makeReactive(target: any) {
    return shallowReactive(target);
  }

  makeDeepReactive(target: any) {
    return reactive(target);
  }
}
import { defineConfig } from "revite";
import { VueReactivityService } from "~/services/reactivity/versions/vue";

export default defineConfig({
  logger: {
    level: "debug",
  },
  reactivity: {
    service: VueReactivityService,
  },
  packages: [import("/~/packages/ViewsPackage")],
  providers: [
    import("/~/providers/EventServiceProvider"),
    import("/~/services/render/RenderServiceProvider"),
    import("/~/services/router/RouterServiceProvider"),
    import("/~/services/auth/AuthServiceProvider"),
  ],
  config: {
    router: {
      service: () => import("/~/services/router/versions/VueRouterService"),
    } as Service.Router.Config,
  },
});

config/prod/manifest.ts

export const config: Service.Manifest.Config = {
  service: () => import("/~/services/manifest/versions/ManifestServiceV1"),
  env: import.meta.env.VITE_ENV,
  keys: {
    recaptcha: "000",
  },
};

There are two ways to load the configuration into Revite.

  • Directly Load directory in source code.
import { revite } from "revite";

revite.bootstrap(import.meta.globEager("./config/default/*.ts"));
  • Apply Vite plugin

vite.config.ts

import { defineConfig } from "vite";
import revite from "revite/plugin";

export default defineConfig({
  plugins: [
    revite({
      root: "/src/config",
      use: process.env.TARGET_CONFIG as string,
    }),
  ],
});

main.ts

import { revite } from "revite";
import config from "@revite/config";

revite.bootstrap(config);

Then start the application with:

env TARGET_CONFIG=prod yarn dev

config/prod directory will be loaded

Service Providers

  • Service providers are designed to link contracts to the services.
  • Also, they can be used to bootstrap any code that you need.
import {
  BootContext,
  BeforeBootContext,
  RegisterContext,
  ServiceProvider,
} from "revite";
import { AuthServiceContract } from "/~/services/auth";
import { UiServiceContract } from "/~/services/ui";

export class AuthServiceProvider extends ServiceProvider {
  register(ctx: RegisterContext) {
    const config: Service.Auth.Config = ctx.config("auth");

    ctx.bind(AuthServiceContract).to({
      service: config.service,
      reactive: true,
      singleton: true,
    });
  }

  async beforeBoot(ctx: BeforeBootContext) {
    const uiService = await ctx.resolve(UiServiceContract);

    await uiService.setFetchStatus();
  }

  async boot(ctx: BootContext) {
    const authService = await ctx.resolve(AuthServiceContract);

    return authService.fetchUser();
  }
}

ServiceProvider.register

Synchronous method to register services, bind events, and configuration to them.

export interface ResolveOptions {
  loaded?: boolean;
}

interface RegisterContext {
  resolve<T extends AbstractConstructor>(
    contract: T,
    options?: ResolveOptions,
  ): Promise<InstanceType<T>>;

  resolveIfExist<T extends AbstractConstructor>(
    contract: T,
    options?: ResolveOptions,
  ): Promise<InstanceType<T> | undefined>;

  on(
    event: EventConstructor,
    listen: ListenerConstructor | ListenerConstructor[],
  ): void;

  config<T>(name: string): T;

  bind<T extends AbstractConstructor>(contract: T): BindContext<T>;
}

ctx.bind usage

// The service itself is a contract
ctx.bind(HomeState).to({
  reactive: true,
  singleton: true,
  // The factory is used when service contain links to other
  async factory({Service}) {
    const dashboardService = await ctx.resolve(DashboardService)

    return () => new Service({
      dashboardService,
    })
  },
})

ctx.bind(AuthServiceContract).to({
  service: () => import('/~/services/auth/versions/AuthServiceV1')
  reactive: true,
})

ServiceProvider.beforeBoot

Asynchronous method to inject any code to other services before providers start booting. For example, we want to inject routes to RouterService before it starts routing.

interface BeforeBootContext {
  resolve<T extends AbstractConstructor>(
    contract: T,
    options?: ResolveOptions,
  ): Promise<InstanceType<T>>;

  resolveIfExist<T extends AbstractConstructor>(
    contract: T,
    options?: ResolveOptions,
  ): Promise<InstanceType<T> | undefined>;

  config<T>(name: string): T;
}

ServiceProvider.boot

Asynchronous method to call any code after all providers will be registered and preloaded.

interface BootContext {
  resolve<T extends AbstractConstructor>(
    contract: T,
    options?: ResolveOptions,
  ): Promise<InstanceType<T>>;

  resolveIfExist<T extends AbstractConstructor>(
    contract: T,
    options?: ResolveOptions,
  ): Promise<InstanceType<T> | undefined>;
}

Services

  • Services should be extended from contracts.
  • There are situations when the service itself does not contain any connections and can act as a contract.
import {NotesServiceContract} from '../'

export class MockNotesService extends NotesServiceContract {
...
}

Events and Listeners

import { Event } from "revite";

export class NoteCreatedEvent extends Event {
  constructor(public note: Service.Notes.Note) {
    super();
  }
}
import { Listener, revite } from "revite";
import { NoteCreatedEvent } from "/~/events/NoteCreatedEvent";
import { NotifyServiceContract } from "/~/services/notify";

export class NoteCreatedNotify extends Listener {
  async handle(event: NoteCreatedEvent) {
    const notifyService = await revite.resolve(NotifyServiceContract);

    notifyService.show({
      title: "Note created",
      type: "success",
      text: "ID " + event.note.id,
    });
  }
}

export class NoteCreatedNotifyWithError extends Listener {
  async handle(event: NoteCreatedEvent) {
    const notifyService = await revite.resolve(NotifyServiceContract);

    notifyService.show({
      title: "Note created",
      type: "success",
      text: "ID " + event.note.id,
    });
  }

  handleError(event: NoteCreatedEvent, error: unknown) {
    // handle error
  }
}
import { RegisterContext, ServiceProvider } from "revite";
import { NoteCreatedEvent } from "/~/events/NoteCreatedEvent";
import { NoteCreatedNotify } from "/~/listeners/NoteCreatedNotify";

export class EventServiceProvider extends ServiceProvider {
  register(ctx: RegisterContext) {
    ctx.on(NoteCreatedEvent, [
      NoteCreatedNotify,
      (event: NoteCreatedEvent) => {
        // use event
      },
      () => import("/~/events/NoteCreatedEvent"),
    ]);

    /**
     * Sequential execution of listeners.
     * If some of listeners will throw an error the next execution will be stopped
     */
    ctx.on(NoteCreatedEvent, [NoteCreatedNotify], {
      sequential: true,
      onError(event: NoteCreatedEvent, error) {
        // use error
        // use event
      },
    });

    /**
     * Listeners will be executed one time in 500 ms
     */
    ctx.on(NoteCreatedEvent, [NoteCreatedNotify], {
      wait: 500,
    });
  }
}

Packages

import { Package } from "revite";

export class ViewsPackage extends Package {
  providers = [
    import("/~/views/home/VueHomeViewProvider"),
    import("/~/views/terms/vue/TermsViewProvider"),
    import("/~/views/notes/VueNotesViewProvider"),
    import("/~/views/report/VueReportViewProvider"),
  ];
}

Extensions

Extensions will be loaded only when service requested.

import { defineConfig } from "revite";

export default defineConfig({
  config: {
    dashboard: {
      extend: [() => import("/~/providers/ImageWidgetProvider")],
    } as Service.Dashboard.Config,
  },
});

The extend method called with resolved service as argument.

import { Extension, revite } from "revite";
import { DashboardService } from "/~/services/dashboard";
import { NotesView } from "/~/views/notes/NotesView";

export class ImageWidgetProvider extends Extension {
  async extend(service: DashboardService) {
    const notesView = await revite.resolve(NotesView);

    service.registerLink({
      route: notesView.getRootRoute(),
      title: "Extended image",
      description:
        "Doloribus dolores nostrum quia qui natus officia quod et dolorem. Sit repellendus qui ut at blanditiis et quo et molestiae.",
      icon: {
        name: "pencil",
        class: "text-green-500 bg-green-50",
      },
      order: 100,
    });
  }
}

Boot scenarios

import { defineConfig } from "revite";

export default defineConfig({
  preload: [
    import("/~/services/auth/AuthServiceProvider"),
    /**
     * this provider will load next scenario
     */
    import("/~/providers/BootstrapProvider"),
  ],
  providers: [
    import("/~/services/ui/UiServiceProvider"),
    import("/~/services/notify/NotifyServiceProvider"),
  ],
  next: {
    authorized: () => ({
      preload: [
        import("/~/services/router/RouterServiceProvider"),
        import("/~/services/render/RenderServiceProvider"),
      ],
      packages: [import("/~/packages/ViewsPackage")],
      providers: [
        import("/~/services/dashboard/DashboardServiceProvider"),
        import("/~/providers/EventServiceProvider"),
        import("/~/services/notes/NotesServiceProvider"),
      ],
    }),
    unauthorized: () => ({
      preload: [
        import("/~/services/router/RouterServiceProvider"),
        import("/~/services/render/RenderServiceProvider"),
      ],
      providers: [
        import("/~/services/dashboard/DashboardServiceProvider"),
        import("/~/views/home/VueHomeViewProvider"),
        import("/~/views/report/VueReportViewProvider"),
      ],
    }),
  },
  config: {
    router: {
      service: () => import("/~/services/router/versions/VueRouterService"),
    } as Service.Router.Config,
    render: {
      service: () => import("/~/services/render/versions/vue/VueRenderService"),
    } as Service.Render.Config,
    notes: {
      service: () => import("/~/services/notes/versions/MockNotesService"),
    } as Service.Notes.Config,
    auth: {
      service: () => import("/~/services/auth/versions/MockLoggedAuthService"),
    } as Service.Auth.Config,
  },
});
import { revite, ServiceProvider } from "revite";
import { AuthServiceContract } from "/~/services/auth";

export class BootstrapProvider extends ServiceProvider {
  async boot(ctx) {
    const authService = await ctx.resolve(AuthServiceContract);

    if (authService.isLoggedIn) {
      return revite.next("authorized");
    }

    return revite.next("unauthorized");
  }
}

Built-in contracts

These contracts are parts of Revite and can be configured in the main section.

LoggerServiceContract

import { defineConfig } from "revite";
import { CustomLoggerService } from "/~/services/logger";

export default defineConfig({
  logger: {
    service: CustomLoggerService,
    level: "debug", // 'info' | 'warn' | 'error'
  },
});

ReactivityServiceContract

import { defineConfig } from "revite";
import { VueReactivityService } from "revite/services/VueReactivityService";

export default defineConfig({
  reactivity: {
    service: VueReactivityService,
  },
});