JSPM

hasura-node-types

0.2.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 17
  • Score
    100M100P100Q47831F
  • License MIT

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

Package Exports

  • hasura-node-types
  • hasura-node-types/lib/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (hasura-node-types) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

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": "example@domain.com", 
    "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": "example@domain.com", 
    "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": "example@domain.com", "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