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