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

now-integration

v1.1.0

Published

A framework that allows better code structure and routing for ZEIT Integrations

Downloads

53

Readme

now-integration

npm install size

A framework that allows better code structure and routing for ZEIT integrations.

Inspiration

After reading the code of a bunch of integrations and trying write some myself, I noticed that the main body of the withUiHook handler had a ton of logic and it became hard to read. Also there was logic that was repeated between actions and even between integrations. Therefore, as I was working on my first integration, I started abstacting some of that logic. After seeing how well it worked for me, I decided to make it a package so I can use it to develop future integration.

NOTE: This is very much WIP. I started developing it during the ZEIT Hackathon, so I had limited time. There's full TypeScript support but cleaner code and better docs coming soon

Install

$ yarn add now-integration

or

$ npm install now-integration

Usage

The main part of the package is the Integration class. This class is the backbone of the application.

import Integration, {requireProject} from 'now-integration';
import {htm} from '@zeit/integration-utils';

const app = new Integration({
  defaultRoute: ({utils}) => htm`<Page>You are logged in! Your token is ${utils.projectStore.token}</Page>`
});

app.extend(requireProject());

app.use('login/:method', async ({utils, zeitClient, payload}, next) => {
  if (utils.params.method === 'token') {
    const token = utils.get('token');

    utils.projectStore.token = token;
    await utils.saveStore();

    next();
  } else {
    // Login some other way
  }
});

app.use(async ({utils}, next) => {
  if (utils.projectStore.token) {
    next();
  } else {
    return utils.renderRoute('view/login');
  }
});

app.render('view/login', async ({payload, zeitClient, utils}) => {
  return htm`
    <Page>
      <H2>Please login to continue</H2>
      <Input name="token" label="token"/>
      <Button action="login/token">Save Token</Button>
    </Page>
  `;
});

export default app.handler;

API

new Integration(options)

options

Type: object

defaultRoute

Type: [handler](#handler)

A handler that will be called if no other route matches. Can be used as the default view route.

integration.use([pattern], ...handlers)

Adds middleware handler(s) to be ran. These usually don't end the call, but handle actions and provide context. If needed they can intercept the flow and end the call.

pattern

Type: string | string[] Optional

A pattern or a list of patterns to match against the action string. The handler(s) will only be called if at least one pattern matches.

If the pattern includes a parameter login/:method, the parameters will be included in the utils object under params

If no pattern is provided, the handler(s) will be called on every action

handlers

Type: [handler](#handler)

The handler(s) that will be called.

The middleware handlers receive an extra parameter next, which they can call to pass the rendering to the next handler. If the handler needs to stop the flow, it can return the htm element instead of calling next(). That will end the call without any other handler being called.

integration.render(pattern, ...handlers)

Adds view handlers. These handlers need to return a valid htm element. That element will be returned from the call.

pattern

Type: string | string[]

A pattern or a list of patterns to match against the action string. The handler(s) will only be called if at least one pattern matches.

If the pattern includes a parameter login/:method, the parameters will be included in the utils object under params

Note: at least one pattern is required for these handlers

handlers

Type: [handler](#handler)

The handler(s) that will be called.

Unlike middleware handlers, these handlers have to returna valid htm element that will be return from to the UI hook.

integration.extend(plugin)

Extend the funcionality of the integration by adding a plugin. All this does is group some middleware and view handlers based on the purpose they serve for cleaner code

plugin

Type: (app: Integration) => void

A function that will be called with the integration instance. In this function, the plugin can add middleware or view handlers.

integration.handler

Type: (req, res) => void

This is a regular http server handler that can be exported from the main file. It includes the withUiHook helper provided by @zeit/integration-utilsintegration-utils).

handler

Type: (options) => Promise<any>

A handler that returns a valid htm element

Middleware handlers receive an extra parameter next that they can call instead of returning a value: (options, next) => Promise<any | void>

Handlers are called in the order they were added. First all the middleware ones and then the view ones until one matches. Be mindful about the order in which you add them.

optons

Type: object

zeitClient

Type: ZeitClient

The client provided by withUiHook of @zeit/integration-utils

payload

Type: object

The payload provided by withUiHook of @zeit/integration-utils

utils

Type: [utils](#utils)

A class with various utilities described below

utils

The utils class available to every handler

utils.context

Type: object

An object that starts empty at the start of the call and will be passed to every handler

Can be used to pass information downstream

utils.params

Type: object Optional

An object that contains key/value pairs for all params matched in the handler's pattern

For example an action view/login/token matched by the pattern view/login/:method would yield:

console.log(utils.params);

// => {method: 'token'}
utils.renderRoute

Type: (route) => Promise<any>

A method that will render the provided route and return the result. This can be used when handlers want to terminate the flow early or redirect it:

if (!token) {
  return utils.renderRoute('login');
} else {
  // Render the regular view
  next();
}
route

Type: string

The exact route to render

Note: The route will try to match a view handler exactly. Won't match middleware handlers

utils.store

Type: object

The result of await zeitClient.getMetadata(). Will be passed to every handler

utils.projectStore

Type: object

Equivalent to store[projectId]. A convenient way to save project-scoped data

utils.saveStore

Type: () => Promise<object>

Equivalent of calling await zeitClient.saveMetadata(utils.store) There is no need to set the result of the call back to the store

utils.store.token = 'my-token';
utils.projectStore.token = 'my-project-token';
await utils.saveStore();

// ...
utils.get

Type: (key) => string

Equivalent to payload.clientState[key]

utils.projectId

Type: string Optional

Equivalent to payload.projectId

utils.action

Type: string

Equivalent to payload.action

The package also includes two plugins for very common functionalities:

requireProject([options])

  import Integration, {requireProject} from 'now-integration';

  const app = new Integration({defaultRoute});

  app.extend(requireProject());

  export default app.handler;

Adds middleware that will ensure the integration is scoped to a project. If it is not, it will render a view including a project switcher.

options

Type: object Optional

SwitchProjectView

Type: htm Component

View to render when the user is not scoped to a project instead of the default one

Takes no props

requireTokenLogin([options])

  import Integration, {requireTokenLogin} from 'now-integration';

  const app = new Integration({defaultRoute});

  app.extend(requireTokenLogin({
    projectScoped: true,
    tokens: [{
      key: 'appKey',
      name: 'You application key'
    }, {
      key: 'appSecret',
      name: 'Your application secret'
    }],
    testTokens: async (tokens) => {
      try {
        await myClient.ensureValidTokens(tokens.appKey, tokens.appSecret);
        return false;
      } catch (error) {
        return 'Something went wrong';
      }
    }
  }));

  export default app.handler;

Adds middleware that will ensure the user has logged in by providing some token(s) before letting them access any of the routes.

options

Type: object Optional

projectScoped

Type: boolean Default: false

Wether or not the plugin should require a different token for each project.

Usually used together with [requireProject](#requireProject)

tokens

Type: { key: string, name: string }[] Default: [{ key: 'token', name: 'API Token' }]

A list of tokens to request from the user

key

Type: string

The key to use when saving the token in the store

name

Type: string

The label to display for the <Input/> element when requesting the token

testTokens

Type: (tokens) => Promise<false | string> Default: () => false

A function that will be called with the tokens when the user submits the form.

It should return false if the tokens are valid, or a string with a message to be displayed to the user when asking for the tokens again.

The tokens will not be saved until testTokens returns false

tokens

Type: {[key: string]: string}

An object containing all the tokens. The keys used are the ones provided in the [tokens](#tokens) object and the values are the user's inputs

LoginView

Type: htm Component

View to render if the user doesn't have all the required tokens instead of the default one.

props
  • error (string) - An error message
  • message (string) - A informational message (You have logged out successfully when the user logs out)
  • title (string) - A title to appear above the form
  • tokens (object) - The tokens to display

Actions

This plugin provides the following action:

logout - Use this to erase the user's token and bring them back to the login screen

htm`<Button action="logout">Logout</Button>`

Maintainers