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

hasura-node-types

v0.2.1

Published

Add Hasura types, event trigger and action wrapper for common web frameworks

Downloads

50

Readme

Hasura Node Types

Integrate type-safe nodejs backend application with Hasura, from TypeScript with love

Installation

$ npm install --save hasura-node-types

Types

WhereBoolExp

Generic where condition type, with the help of Typescript inference type.

type Profile = {
  id: number
  name: string
  phone: {
    code: string
    phoneNumber: string
  }
  addresses: [{
    street: string
    city: string
  }]
};

type ProfileBoolExp = WhereBoolExp<Profile>;

const profileWhere: ProfileBoolExp = {
  _and: [{ id: { _is_null: true } }],
  id: { _eq: 10 },
  name: { _ilike: "10" },
  phone: {
    phoneNumber: { _in: ["012345678", "987654321"] }
  },
  addresses: {
    street: { _like: "%a%" }
  }
};

Event Trigger

Event payload follows Hasura docs

import { 
  HasuraEventPayload, 
  // trigger event operations
  HasuraEventUpdate, 
  // HasuraEventInsert, 
  // HasuraEventDelete,
  // HasuraEventManual,
} from "hasura-node-types";

type EventPayload = HasuraEventPayload<HasuraEventUpdate<{
  email: string
  password: string
}>>

const payload: EventPayload = res.body;

// or you can use default any payload
const payload: HasuraEventPayload = res.body;

Action

Action payload follows Hasura docs

import { HasuraEventPayload, HasuraEventUpdate } from "hasura-node-types";

type LoginInput = {
  readonly email: string
  readonly password: string
};


type ActionPayload = HasuraActionPayload<LoginInput, typeof ACTION_LOGIN>;

const payload: ActionPayload = res.body;

// or you can use default any payload
const payload: HasuraActionPayload = res.body;

With Express

Thank to GraphQL Engine payload structure, we can apply Factory Pattern that use single endpoint for multiple events, distinguished by event/action name

type WithHasuraOptions<Ctx extends AnyRecord = AnyRecord> = {
  readonly logger?: Logger
  readonly debug?: boolean
  readonly logRequestBody?: boolean
  readonly logResponseData?: boolean
  readonly context?: Ctx
};

const we = withExpress([options])

This instance is Action and Event Trigger wrappers for Express handlers

Options

  • logger: Logging instance, use console.log by default. Support common libraries that implement logger interface (winston, bunyan)
  • debug: show response data when printing logging. This field is also included in context
  • context: extra context data
  • logRequestBody: should log request body or not. This option is always true if debug is true
  • logResponseData: should log response data or not. This option is always true if debug is true

useActions([handlerMap])

Wrap Express handler with pre-validation, select and run action function from handler map


import { withExpress } from "hasura-node-types";
const ACTION_LOGIN = "login";

type LoginInput = {
  readonly email: string
  readonly password: string
};

type LoginOutput = LoginInput;

const loginAction: HasuraActionExpressHandler<
  HasuraActionPayload<LoginInput, typeof ACTION_LOGIN>,
  LoginOutput
> = (ctx, { input }) => Promise.resolve(input);

const handlerMap = {
  [ACTION_LOGIN]: loginAction
};

export default withExpress().useActions(handlerMap);

With HasuraActionHandler function is defined as:

type HasuraActionExpressContext = {
  logger: Logger
  request: Request
  // extra contexts
}

type HasuraActionHandler<Payload, Resp> = (ctx: HasuraActionExpressContext, payload: Payload) => Promise<Resp>

useAction([handler])

Use this function If you prefer using multiple routes instead


const we = withExpress();
const router = express.Router();

router.post("/actions/login", we.useAction(loginAction));
router.post("/actions/logout", we.useAction(logoutAction));

useEvents([handlerMap])

Wrap Express handler with pre-validation, select and run event trigger functions from handler map.

Note: you can use default or * as default fallback handler


const EVENT_TRIGGER_UPDATE_USER = "update_user";

type UserInput = {
  readonly email: string
  readonly password: string
};

const userUpdateEvent: HasuraEventExpressHandler<
  HasuraEventUpdate<UserInput>,
  UserInput,
  typeof EVENT_TRIGGER_UPDATE_USER
> = (_, { event }) => Promise.resolve(event.data.new);

const handlerMap = {
  [EVENT_TRIGGER_UPDATE_USER]: userUpdateEvent
  // default event handler
  default: () => Promise.resolve({ 
    "message": "default"
  })
};

export default withExpress().useEvents(handlerMap);

useEvent([handler])

Use this function If you prefer using multiple routes instead


const we = withExpress();
const router = express.Router();

router.post("/events/update-user", we.useEvent(updateUser));
router.post("/events/delete-user", we.useEvent(deleteUser));

Logging

Logging structure follows GraphQL engine styles, using JSON format

Note": Response

  • Success action log
{
  "action_name": "login",
  "session_variables": { "x-hasura-role": "anonymous" },
  "request_headers": {
    "host": "127.0.0.1:40763",
    "accept-encoding": "gzip, deflate",
    "user-agent": "node-superagent/3.8.3",
    "content-type": "application/json",
    "content-length": "176",
    "connection": "close"
  },
  "request_body": { 
    "email": "[email protected]", 
    "password": "123456" 
  },
  "latency": 4,
  "level": "info",
  "message": "executed login successfully",
  "response": null,
  "http_code": 200
}
  • Failure action log
{
  "action_name": null,
  "session_variables": { "x-hasura-role": "anonymous" },
  "request_headers": {
    "host": "127.0.0.1:33013",
    "accept-encoding": "gzip, deflate",
    "user-agent": "node-superagent/3.8.3",
    "content-type": "application/json",
    "content-length": "50",
    "connection": "close"
  },
  "request_body": null,
  "latency": 0,
  "level": "error",
  "message": "empty hasura action name",
  "error": {
    "code": "validation_error",
    "details": null
  },
  "http_code": 400
}
  • Success event trigger log
{
  "request_body": { 
    "email": "[email protected]", 
    "password": "123456" 
  },
  "request_header": {
    "host": "127.0.0.1:36825",
    "accept-encoding": "gzip, deflate",
    "user-agent": "node-superagent/3.8.3",
    "content-type": "application/json",
    "content-length": "50",
    "connection": "close"
  },
  "trigger_name": null,
  "latency": 0,
  "level": "error",
  "message": "empty hasura event trigger id",
  "error":{
    "code": "validation_error",
    "details": null
  },
  "http_code": 400
}
  • Failure event trigger log
{
  "request_body": {
    "id": "2020-05-08T08:55:49.946Z",
    "event": { 
      "session_variables": { "x-hasura-role": "anonymous" },
      "op": "UPDATE", 
      "data": { "email": "[email protected]", "password": "123456" } 
    },
    "created_at": "2020-05-08T08:55:49.946Z",
    "trigger": { "name": "update_user" },
    "table": { "name": "users", "schema": "public" }
  },
  "request_header": {
    "host": "127.0.0.1:35223",
    "accept-encoding": "gzip, deflate",
    "user-agent": "node-superagent/3.8.3",
    "content-type": "application/json",
    "content-length": "342",
    "connection": "close"
  },
  "trigger_name": "update_user",
  "latency": 1,
  "level": "info",
  "message": "executed trigger update_user successfully",
  "response": null,
  "http_code": 200
}

Note: request body and response data can be null by withExpress options

Common Getters

Action

// get action user ID
function getActionUserID(payload: HasuraActionPayload): string | null

// get action user role
function getActionUserRole(payload: HasuraActionPayload): string | null

Event Trigger

// get event user ID
function getEventUserID(payload: HasuraEventPayload): string | null 

// get event user role
function getEventUserRole(payload: HasuraEventPayload): string | null

Extra Resources