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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kronor/hasura-graphql

v0.1.2

Published

Typed GraphQL and Hasura AST primitives: filter expressions, query documents, row-typed DSLs and schema type generation.

Readme

@kronor/hasura-graphql

Typed GraphQL and Hasura AST primitives for declarative, schema-driven UIs.

This package holds the parts of @kronor/dtv that are not about tables: building and comparing Hasura boolean expressions, describing what to select from a Hasura root field, rendering that to a GraphQL query string, and the type-level machinery that ties both to a generated row type.

It has no UI dependencies — graphql is its only runtime dependency.

Install

npm install @kronor/hasura-graphql

What's in it

hasura/ — boolean expressions

HasuraFilterExpression is an algebra you can build, normalize and compare before lowering it to the nested *_bool_exp shape Hasura expects.

import { Hasura, hasuraFilterExpressionToObject } from '@kronor/hasura-graphql';

const expr = Hasura.and(
    Hasura.condition('status', Hasura.eq('PAID')),
    Hasura.scope('customer', Hasura.condition('email', Hasura.ilike('%@example.com')))
);

hasuraFilterExpressionToObject(expr);
// { _and: [ { status: { _eq: 'PAID' } }, { customer: { email: { _ilike: '%@example.com' } } } ] }

Empty branches are dropped and single-member _and / _or nodes collapse, so conditionally-built expressions stay clean without special-casing at the call site. hasuraFilterExpressionsAreEqual compares two expressions structurally, ignoring member order and scope/path spelling.

query/ — selection sets and documents

Query nodes describe a selection: valueQuery (scalar), objectQuery (nested object) and arrayQuery (related collection, which also carries where / orderBy / distinctOn / limit). fieldAlias adds GraphQL aliasing.

import {
    valueQuery, objectQuery, arrayQuery,
    buildSelectionSet, renderGraphQLQuery, Hasura
} from '@kronor/hasura-graphql';

const selectionSet = buildSelectionSet([
    { fieldQuery: valueQuery({ field: 'id' }) },
    { fieldQuery: objectQuery({ field: 'customer', selectionSet: [valueQuery({ field: 'email' })] }) },
    { fieldQuery: arrayQuery({
        field: 'lines',
        selectionSet: [valueQuery({ field: 'sku' })],
        where: Hasura.condition('qty', Hasura.gt(0)),
        limit: 10
    }) }
]);

renderGraphQLQuery({
    operation: 'query',
    variables: [{ name: 'conditions', type: 'order_bool_exp!' }],
    rootField: { field: 'orders', args: [{ name: 'where', value: { type: 'variable', name: 'conditions' } }] },
    selectionSet
});

buildSelectionSet de-duplicates structurally identical selections, so several independent consumers of the same row can contribute overlapping selections without emitting duplicate fields. ensureSelectionPath guarantees a field is selected when the caller needs it for its own bookkeeping (a pagination cursor, a record id).

Selections that address the same field but select different things from it are kept side by side by default, since a consumer that declares a whole sub-tree (one per table column, say) means them to stay distinct. Consumers that instead contribute independent field paths want them combined — vendor.name and vendor.id should reach the server as one vendor { name id }. Pass mergeNestedSelections for that, or use mergeSelectionSetItem directly:

buildSelectionSet(inputs, { mergeNestedSelections: true });

Items whose arguments differ — the same collection filtered two ways — are never merged, whatever the option says.

Enum arguments

GraphQL enums cannot be passed as strings: Hasura's order_by directions and distinct_on columns need ASC, not "ASC". graphqlEnumValue marks a value as an enum wherever an argument or literal is accepted, and orderByArgumentValue lowers a whole order_by object for you, upper-casing the directions on the way:

import { graphqlEnumValue, orderByArgumentValue } from '@kronor/hasura-graphql';

rootField: {
    field: 'currencies',
    args: [{ name: 'orderBy', value: orderByArgumentValue({ code: 'asc' }) }]
}
// currencies(orderBy: {code: ASC})

Enum values and variable references both render bare, including inside the operator values of a where clause — so a filter can compare against a query variable (Hasura.eq(graphqlVariableReference('id'))).

Several root fields

renderGraphQLQuery takes either document shape. Give it rootFields to fetch unrelated collections in one round trip — an entity together with the option lists a form needs, say. Root fields naming the same collection need distinct aliases; rootFieldsOf normalizes either shape to a list.

renderGraphQLQuery({
    operation: 'query',
    variables: [{ name: 'id', type: 'bigint!' }],
    rootFields: [
        { field: 'orders', args: [{ name: 'where', value: { id: { _eq: graphqlVariableReference('id') } } }],
          selectionSet: buildSelectionSet([{ fieldQuery: valueQuery({ field: 'reference' }) }]) },
        { field: 'currencies', alias: 'currencyOptions',
          args: [{ name: 'orderBy', value: orderByArgumentValue({ code: 'asc' }) }],
          selectionSet: buildSelectionSet([{ fieldQuery: valueQuery({ field: 'code' }) }]) }
    ]
});

dsl/ — row-typed builders

Given a row type — typically generated from your schema — the builders constrain field paths and selection sets to what actually exists.

import { queryForRowType, hasuraDSLforRowType, rowType } from '@kronor/hasura-graphql';

type Order = {
    id: string;
    status: string;
    customer: { email: string | null } | null;
    lines: Array<{ sku: string; qty: number | null }>;
};

const q = queryForRowType(rowType<Order>());

q.value({ field: 'id' });
q.object({ field: 'customer', selectionSet: c => [c.value({ field: 'email' })] });
q.array({
    field: 'lines',
    selectionSet: l => [l.value({ field: 'sku' })],
    // `h` is scoped to the line element type
    where: h => h.condition('qty', h.gt(0))
});

const h = hasuraDSLforRowType<Order>();
h.condition('customer.email', h.ilike('%@example.com')); // checked against Order

FieldPath<Row> and PathValue<Row, Path> are available directly if you need to build your own row-typed APIs on top.

typegen — schema to TypeScript

The @kronor/hasura-graphql/typegen entry point holds the schema-side half of type generation: introspecting an endpoint and rendering GraphQL types as TypeScript. Deciding which types to generate is left to the caller, since that depends on how your project declares its views or forms.

import { fetchSchema, collectReachableTypes, renderTsFromSchema, unwrapCollectionElementType }
    from '@kronor/hasura-graphql/typegen';

const schema = await fetchSchema({ endpoint, headers });
const rowType = unwrapCollectionElementType(schema.getQueryType()!.getFields()['orders'].type);
const ts = renderTsFromSchema(collectReachableTypes(schema, [rowType]), { scalars: { uuid: 'string' } });

Development

This package lives in the declarative-table-view monorepo.

npm install            # from the repo root
npm run build   --workspace @kronor/hasura-graphql
npm run test-unit --workspace @kronor/hasura-graphql