hasura-node-types
TypeScript icon, indicating that this package has built-in type declarations

0.2.1 • Public • Published

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

Package Sidebar

Install

npm i hasura-node-types

Weekly Downloads

64

Version

0.2.1

License

MIT

Unpacked Size

59.5 kB

Total Files

17

Last publish

Collaborators

  • hgiasac