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

fraiseql

v2.8.0

Published

FraiseQL v2 - Compiled GraphQL execution engine (schema authoring + HTTP client)

Readme

FraiseQL v2 - TypeScript Schema Authoring

Compiled GraphQL execution engine - Schema authoring in TypeScript

FraiseQL v2 is a high-performance GraphQL engine that compiles schemas at build-time for zero-cost query execution. This package provides schema authoring in TypeScript that generates JSON schemas consumed by the Rust compiler.

Key Principle: TypeScript is for authoring only - no runtime FFI, no language bindings. Just pure JSON generation.

Architecture

TypeScript Code (decorators)
         ↓
    schema.json
         ↓
 fraiseql-cli compile
         ↓
 schema.compiled.json
         ↓
 Rust Runtime (fraiseql-server)

Installation

npm install fraiseql
# or
yarn add fraiseql
# or
pnpm add fraiseql

Requirements: Node.js 18+

Quick Start

1. Define Types

import * as fraiseql from "fraiseql";

@fraiseql.type()
class User {
  id!: number;
  name!: string;
  email!: string;
}

// Register fields (TypeScript doesn't preserve type info at runtime)
fraiseql.registerTypeFields("User", [
  { name: "id", type: "Int", nullable: false },
  { name: "name", type: "String", nullable: false },
  { name: "email", type: "String", nullable: false },
]);

2. Define Queries

@fraiseql.query({ sqlSource: "v_user" })
function users(limit: number = 10, offset: number = 0): User[] {
  throw new Error("Not executed");
}

fraiseql.registerQuery(
  "users",
  "User",
  true,    // returns list
  false,   // not nullable
  [
    { name: "limit", type: "Int", nullable: false, default: 10 },
    { name: "offset", type: "Int", nullable: false, default: 0 },
  ],
  "Get all users",
  { sql_source: "v_user" }
);

3. Define Mutations

@fraiseql.mutation({ sqlSource: "fn_create_user", operation: "CREATE" })
function createUser(name: string, email: string): User {
  throw new Error("Not executed");
}

fraiseql.registerMutation(
  "createUser",
  "User",
  false,   // single item
  false,   // not nullable
  [
    { name: "name", type: "String", nullable: false },
    { name: "email", type: "String", nullable: false },
  ],
  "Create a new user",
  { sql_source: "fn_create_user", operation: "CREATE" }
);

4. Export Schema

// At end of file
if (require.main === module) {
  fraiseql.exportSchema("schema.json");
}

5. Compile

# Generate compiled schema
fraiseql-cli compile schema.json

# Start server
fraiseql-server --schema schema.compiled.json --port 3000

API Reference

Decorators

@Type(config?)

Mark a class as a GraphQL type.

@fraiseql.type()
class User {
  id!: number;
  name!: string;
}

Note: Decorators alone don't capture field types. Use registerTypeFields() to provide field metadata.

@Query(config)

Mark a function as a GraphQL query.

@fraiseql.query({ sqlSource: "v_user" })
function users(limit: number = 10): User[] {
  throw new Error("Not executed");
}

Config Options:

  • sqlSource: SQL view/table name (required for data operations)
  • autoParams: Auto-parameter configuration
  • Other custom configuration

@Mutation(config)

Mark a function as a GraphQL mutation.

@fraiseql.mutation({ sqlSource: "fn_create_user", operation: "CREATE" })
function createUser(name: string): User {
  throw new Error("Not executed");
}

Config Options:

  • sqlSource: SQL function name (required)
  • operation: "CREATE" | "UPDATE" | "DELETE" | "CUSTOM"
  • Other custom configuration

@FactTable(config)

Mark a class as a fact table for analytics.

@fraiseql.FactTable({
  tableName: "tf_sales",
  measures: ["revenue", "quantity"],
  dimensionPaths: [
    {
      name: "category",
      json_path: "data->>'category'",
      data_type: "text",
    },
  ],
})
@fraiseql.type()
class Sale {
  id!: number;
  revenue!: number;
  quantity!: number;
}

@AggregateQuery(config)

Mark a function as an aggregate query on a fact table.

@fraiseql.AggregateQuery({
  factTable: "tf_sales",
  autoGroupBy: true,
  autoAggregates: true,
})
@fraiseql.query()
function salesAggregate(): Record<string, unknown>[] {
  throw new Error("Not executed");
}

@Subscription(config?)

Mark a function as a GraphQL subscription for real-time events.

Subscriptions in FraiseQL are compiled database event projections sourced from LISTEN/NOTIFY or CDC, not resolver-based.

@fraiseql.Subscription({ topic: "order_events" })
function orderCreated(userId?: string): Order {
  pass;
}

Subscription Configuration

SubscriptionConfig Options:

  • entityType: Entity type being subscribed to (defaults to return type)
  • topic: Optional topic/channel name for filtering events
  • operation: Single event type filter - "CREATE" | "UPDATE" | "DELETE"
  • operations: Multiple event type filters - ["CREATE", "UPDATE", "DELETE"]

Manual Registration:

fraiseql.registerSubscription(
  "orderCreated",        // name
  "Order",               // entityType
  false,                 // nullable
  [
    { name: "userId", type: "String", nullable: true }
  ],                     // filter arguments
  "Subscribe to new orders",
  { topic: "order_events", operation: "CREATE" }
);

Subscription Patterns:

  1. Event Type Filtering - Subscribe to specific operations
fraiseql.registerSubscription(
  "userCreated",
  "User",
  false,
  [],
  "New user registrations",
  { operation: "CREATE" }  // Only CREATE events
);
  1. Topic-Based Subscriptions - Route to different channels
fraiseql.registerSubscription(
  "criticalOrders",
  "Order",
  false,
  [],
  "High-priority orders",
  { topic: "orders.critical", operation: "CREATE" }
);
  1. Filtered Subscriptions - Target specific records
fraiseql.registerSubscription(
  "customerOrders",
  "Order",
  false,
  [{ name: "customerId", type: "ID", nullable: false }],  // Filter by customer
  "Orders for specific customer"
);
  1. Change Data Capture (CDC) - Capture all changes
fraiseql.registerSubscription(
  "userCDC",
  "User",
  false,
  [],
  "All user changes",
  { operations: ["CREATE", "UPDATE", "DELETE"] }
);
  1. Alerts and Notifications - Complex filtering
fraiseql.registerSubscription(
  "unusualOrders",
  "Order",
  false,
  [
    { name: "minAmount", type: "Decimal", nullable: false },
    { name: "timeWindowMinutes", type: "Int", nullable: true }
  ],
  "Alert on high-value orders",
  { operation: "CREATE" }
);

Type System Decorators

enum_(name, values, config?)

Define a GraphQL enum type.

const OrderStatus = fraiseql.enum_("OrderStatus", {
  PENDING: "pending",
  SHIPPED: "shipped",
  DELIVERED: "delivered",
}, {
  description: "Status of an order"
});

Then use in types:

fraiseql.registerTypeFields("Order", [
  { name: "id", type: "ID", nullable: false },
  { name: "status", type: "OrderStatus", nullable: false },
]);

interface_(name, fields, config?)

Define a GraphQL interface - shared fields for multiple types.

const Node = fraiseql.interface_("Node", [
  { name: "id", type: "ID", nullable: false },
  { name: "createdAt", type: "DateTime", nullable: false },
], {
  description: "An object with a globally unique ID"
});

Types can implement interfaces:

fraiseql.registerTypeFields("User", [
  { name: "id", type: "ID", nullable: false },
  { name: "createdAt", type: "DateTime", nullable: false },
  { name: "name", type: "String", nullable: false },
]);

union(name, memberTypes, config?)

Define a GraphQL union - polymorphic return type.

const SearchResult = fraiseql.union("SearchResult",
  ["User", "Post", "Comment"],
  { description: "Result of a search query" }
);

Then use in queries:

fraiseql.registerQuery(
  "search",
  "SearchResult",  // Returns union
  true,            // returns list
  false,           // not nullable
  [{ name: "query", type: "String", nullable: false }],
  "Search across content"
);

input(name, fields, config?)

Define a GraphQL input type - structured parameters.

const CreateUserInput = fraiseql.input("CreateUserInput", [
  { name: "email", type: "Email", nullable: false },
  { name: "name", type: "String", nullable: false },
  { name: "role", type: "String", nullable: false, default: "user" },
], {
  description: "Input for creating a new user"
});

Use in mutations:

fraiseql.registerMutation(
  "createUser",
  "User",
  false,
  false,
  [{ name: "input", type: "CreateUserInput", nullable: false }],
  "Create a new user"
);

Field-Level Metadata

Add access control, deprecation markers, and documentation to individual fields:

field(options)

Create field metadata for use with registerTypeFields():

fraiseql.registerTypeFields("User", [
  { name: "id", type: "ID", nullable: false },
  {
    name: "salary",
    type: "Decimal",
    nullable: false,
    requiresScope: "read:User.salary",
    description: "Annual salary (requires HR scope)"
  },
  {
    name: "oldEmail",
    type: "String",
    nullable: true,
    deprecated: "Use email instead",
    description: "Legacy email field (deprecated)"
  }
]);

Field Metadata Options:

  • requiresScope: string | string[] - JWT scope(s) required to access this field (field-level access control)
  • deprecated: boolean | string - Mark field as deprecated. Pass a string with migration guidance.
  • description: string - Field documentation (appears in GraphQL schema)

Use Cases:

  1. PII Protection: Require specific scopes for sensitive fields
{
  name: "ssn",
  type: "String",
  nullable: false,
  requiresScope: "pii:read"  // Only users with pii:read scope can query this
}
  1. API Versioning: Deprecate fields with migration guidance
{
  name: "oldPrice",
  type: "Decimal",
  nullable: true,
  deprecated: "Use pricing.current instead - structure moved to pricing object"
}
  1. Schema Documentation: Add rich field descriptions
{
  name: "discount",
  type: "Decimal",
  nullable: false,
  description: "Discount percentage. Access requires orders:view_discounts scope.",
  requiresScope: "orders:view_discounts"
}

Manual Registration Functions

When decorators alone don't provide enough type information:

registerTypeFields(typeName, fields, description?)

Register type field definitions.

fraiseql.registerTypeFields("User", [
  { name: "id", type: "Int", nullable: false },
  { name: "name", type: "String", nullable: false },
  { name: "email", type: "String", nullable: true },
]);

registerQuery(name, returnType, returnsList, nullable, args, description?, config?)

Register a query with full metadata.

fraiseql.registerQuery(
  "users",
  "User",
  true,      // returns list
  false,     // not nullable
  [
    { name: "limit", type: "Int", nullable: false, default: 10 },
  ],
  "Get all users",
  { sql_source: "v_user" }
);

registerMutation(name, returnType, returnsList, nullable, args, description?, config?)

Register a mutation with full metadata.

fraiseql.registerMutation(
  "createUser",
  "User",
  false,     // single item
  false,     // not nullable
  [
    { name: "name", type: "String", nullable: false },
  ],
  "Create a new user",
  { sql_source: "fn_create_user", operation: "CREATE" }
);

Schema Export

exportSchema(outputPath, options?)

Export the schema to a JSON file.

fraiseql.exportSchema("schema.json", { pretty: true });

getSchemaDict()

Get the schema as a JavaScript object.

const schema = fraiseql.getSchemaDict();
console.log(schema.types);
console.log(schema.queries);

exportSchemaToString(options?)

Export schema to a JSON string.

const json = fraiseql.exportSchemaToString({ pretty: true });
console.log(json);

Supported GraphQL Types

Scalars

  • Int - 32-bit integer
  • Float - Floating point number
  • String - Text string
  • Boolean - True/False
  • ID - Unique identifier

Modifiers

  • T[] - List type (maps to [T!] in GraphQL)
  • T | null - Nullable type
  • T | undefined - Optional parameter

Type Mapping

TypeScript types are converted to GraphQL types:

// TypeScript    →  GraphQL
number          →  Float
string          →  String
boolean         →  Boolean
SomeClass       →  SomeClass (custom type)
T[]             →  [T!]      (list)
T | null        →  T         (nullable)
T | undefined   →  T         (optional param)

Analytics Features

Fact Tables

Fact tables are special analytics tables with:

  • Measures: Numeric columns for aggregation (SUM, AVG, COUNT)
  • Dimensions: JSONB column for flexible GROUP BY
  • Denormalized Filters: Indexed columns for fast WHERE clauses
@fraiseql.FactTable({
  tableName: "tf_sales",           // Must start with "tf_"
  measures: ["revenue", "cost"],   // Numeric columns
  dimensionPaths: [
    {
      name: "category",
      json_path: "data->>'category'",
      data_type: "text",
    },
  ],
})
@fraiseql.type()
class Sale {
  id!: number;
  revenue!: number;
  cost!: number;
  customerId!: string;
}

Aggregate Queries

Queries that perform GROUP BY aggregations on fact tables:

@fraiseql.AggregateQuery({
  factTable: "tf_sales",
  autoGroupBy: true,       // Auto-generate groupBy fields
  autoAggregates: true,    // Auto-generate aggregate functions
})
@fraiseql.query()
function salesSummary(): Record<string, unknown>[] {
  throw new Error("Not executed");
}

These queries support:

  • groupBy: Dimensions and temporal buckets
  • aggregates: COUNT, SUM, AVG, MIN, MAX
  • where: Pre-aggregation filters
  • having: Post-aggregation filters
  • orderBy: Sort results
  • Pagination: limit, offset

Examples

See the examples/ directory:

  • basic_schema.ts - Simple CRUD queries and mutations
  • analytics_schema.ts - Fact tables and aggregate queries
  • enums-example.ts - Enum definitions and usage
  • types-advanced.ts - Comprehensive type system example (enums, interfaces, unions, input types)
  • unions-interfaces-example.ts - Interfaces, unions, and polymorphic queries
  • field-metadata.ts - Field-level access control, deprecation, and documentation
  • subscriptions.ts - Real-time subscriptions: event filtering, topics, CDC, alerts
  • comprehensive-example.ts - Full-featured schema with all FraiseQL capabilities

Run examples:

npm run example:basic         # Generate basic schema
npm run example:analytics     # Generate analytics schema
npm run example:enums         # Generate enum example
npm run example:advanced      # Generate advanced types example
npm run example:metadata      # Generate field metadata example
npm run example:subscriptions # Generate subscriptions example

Development

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Watch mode
npm run test:watch

# Lint
npm run lint

# Format code
npm run format

Testing

Tests verify:

  • Type introspection and conversion
  • Schema registration and retrieval
  • Decorator functionality
  • Schema JSON generation
  • Analytics fact tables and aggregate queries
npm test

Troubleshooting

Issue: "Field type information not available"

Cause: TypeScript doesn't preserve type information at runtime by default.

Solution: Use registerTypeFields() or registerQuery()/registerMutation() with explicit type metadata.

// Instead of relying on decorators alone:
fraiseql.registerTypeFields("User", [
  { name: "id", type: "Int", nullable: false },
  // ... other fields
]);

Issue: "Factory not started: fraiseql-cli not found"

Solution: Install the CLI tool:

# Global installation
npm install -g fraiseql-cli

# Or use local version
npx fraiseql-cli compile schema.json

Performance

  • Compile-time: Negligible (< 100ms for typical schemas)
  • Runtime: Zero overhead - SQL is compiled, not interpreted
  • Schema generation: Fast JSON serialization

Architecture Notes

No Runtime FFI

This package generates JSON only. There's no FFI, no native bindings, no runtime dependencies on the Rust engine.

The workflow is:

  1. Write TypeScript with decorators
  2. Run exportSchema() to generate schema.json
  3. Compile with fraiseql-cli to get schema.compiled.json
  4. Deploy compiled schema to Rust runtime

Why Manual Field Registration?

TypeScript's decorator system doesn't preserve generic type parameters at runtime. To provide full type information, we require explicit field registration. This is a limitation of the language, not the framework.

Future versions may use TypeScript 5.2+ metadata if decorators mature in the standard.

License

MIT

Support

Contributing

Contributions welcome! Please follow the contribution guidelines in the main repository.


Remember: FraiseQL TypeScript is for authoring only. Runtime execution happens in the Rust engine.