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

@odata-effect/odata-effect-generator

v0.6.12

Published

Effect-based OData service code generator

Downloads

2,476

Readme

@odata-effect/odata-effect-generator

Code generator for Effect-based OData service clients. Generates type-safe TypeScript code from OData metadata.

Installation

npm install -g @odata-effect/odata-effect-generator
# or
pnpm add -g @odata-effect/odata-effect-generator

Usage

odata-effect-gen generate ./metadata.xml ./generated

Arguments

  • <metadata-path>: Path to OData metadata XML file (required)
  • <output-dir>: Output directory for generated code (required)

Options

  • --service-name: Override service name (defaults to EntityContainer name)
  • --package-name: NPM package name (defaults to @template/-effect)
  • --force: Overwrite existing files
  • --files-only: Generate only source files directly in output-dir (no package.json, tsconfig, src/ subdirectory)

Generated Code

The generator produces:

| File | Description | |------|-------------| | Models.ts | Schema classes for entities and complex types | | QueryModels.ts | Type-safe query paths for filtering and ordering | | Services.ts | CRUD services for all entity sets (using crud factory) | | PathBuilders.ts | Tree-shakable navigation path builders with toPromise | | Operations.ts | Functions/Actions (if present in metadata) | | index.ts | Re-exports all generated code |

Two Ways to Query Data

The generator provides two complementary approaches for querying OData services:

1. Service Functions (Direct Operations)

Best for simple CRUD operations on a single entity set:

import { ProductService } from "./generated"

// Get all products
const products = yield* ProductService.getAll()

// Get by ID
const product = yield* ProductService.getById(123)

// Create
const newProduct = yield* ProductService.create({ name: "Widget", price: 9.99 })

// Update
yield* ProductService.update(123, { price: 12.99 })

// Delete
yield* ProductService.delete(123)

2. Path Builders (Pipe-based Navigation)

Best for navigating relationships with full type safety. Uses branded types to ensure you can only navigate to valid properties:

import { pipe } from "effect"
import {
  People, byKey, trips, planItems, asFlight, bestFriend,
  fetchCollection, fetchOne
} from "./generated"
import { Person, Trip, Flight } from "./generated"

// Navigate through relationships with pipe()
const flights = yield* pipe(
  People,                    // Path<PersonModel, true>  - collection
  byKey("russellwhyte"),     // Path<PersonModel, false> - single entity
  trips,                     // Path<TripModel, true>    - collection
  byKey(0),                  // Path<TripModel, false>   - single entity
  planItems,                 // Path<PlanItemModel, true>
  asFlight,                  // Path<FlightModel, true>  - type cast
  fetchCollection(Flight)    // Execute the query
)

// Get a single entity
const person = yield* pipe(
  People,
  byKey("russellwhyte"),
  fetchOne(Person)
)

// Navigate to a single related entity
const friend = yield* pipe(
  People,
  byKey("russellwhyte"),
  bestFriend,
  fetchOne(Person)
)

Type Safety with Branded Types

The path builders use branded types to ensure type-safe navigation at compile time:

// Path<TEntity, IsCollection> tracks what entity type you're "at"
type Path<TEntity, IsCollection extends boolean = false> = string & {
  readonly _entity: TEntity
  readonly _collection: IsCollection
}

// TypeScript prevents invalid navigation:

// ✅ Valid - trips is a navigation property on Person
pipe(People, byKey("russell"), trips)

// ❌ Compile error - planItems is on Trip, not Person
pipe(People, byKey("russell"), planItems)

// ❌ Compile error - can't byKey on a single entity (not a collection)
pipe(People, byKey("russell"), byKey("other"))

Path Builder Features

| Feature | Example | |---------|---------| | Entity set root | PeoplePath<PersonModel, true> | | Key access | byKey("id") → converts collection to single | | Navigation | trips → type-safe navigation to related entity | | Type casting | asFlight → filter to derived type | | Terminal ops | fetchCollection(Schema), fetchOne(Schema) |

Tree-Shaking

Path builders are fully tree-shakable. Each navigation function is a separate export:

// Only import what you use - unused navigation functions are removed by bundler
import { People, byKey, trips } from "./generated"

Comparison with odata2ts

If you're familiar with odata2ts, here's how the APIs compare:

// odata2ts (method chaining)
const response = await trippinService
  .people("russellwhyte")
  .trips(0)
  .planItems()
  .asFlightCollectionService()
  .query()

// odata-effect (pipe composition)
const flights = yield* pipe(
  People,
  byKey("russellwhyte"),
  trips,
  byKey(0),
  planItems,
  asFlight,
  fetchCollection(Flight)
)

Query Options

Both service functions and path builders support OData query options:

// With service functions
const products = yield* ProductService.getAll({
  $filter: "price gt 10",
  $orderby: "name",
  $top: 10,
  $select: "id,name,price"
})

// With path builders (via fetchCollection/fetchOne)
const myTrips = yield* pipe(
  People,
  byKey("russellwhyte"),
  trips,
  (path) => fetchCollection(Trip)(path, {
    $filter: "budget gt 1000",
    $orderby: "startsAt desc"
  })
)

Type-Safe Query Building

Use the generated QueryModels for type-safe filter and orderby construction:

import { productQuery } from "./generated"

const query = productQuery()
  .filter(q => q.price.gt(10).and(q.name.contains("Widget")))
  .orderBy(q => q.name.asc())
  .select("id", "name", "price")
  .top(10)
  .build()

const products = yield* ProductService.getAll(query)

Promise-Based Usage

For non-Effect environments, use the toPromise function to convert any Effect to a Promise:

import { pipe } from "effect"
import { createODataRuntime } from "@odata-effect/odata-effect-promise"
import { ProductService, toPromise, People, byKey, trips, fetchCollection, Trip } from "./generated"

const runtime = createODataRuntime({
  baseUrl: "https://api.example.com",
  servicePath: "/odata/v4/"
})

// Service functions - pipe through toPromise
const products = await ProductService.getAll().pipe(toPromise(runtime))
const product = await ProductService.getById(123).pipe(toPromise(runtime))

// Path builders - add toPromise at the end of the pipe
const myTrips = await pipe(
  People,
  byKey("russellwhyte"),
  trips,
  fetchCollection(Trip),
  toPromise(runtime)
)

// Don't forget to dispose when done
await runtime.dispose()

Operations (Functions & Actions)

If your OData service defines FunctionImports (V2) or Functions/Actions (V4), they are generated in Operations.ts:

import { Operations } from "./generated"

// V2 FunctionImport
const result = yield* Operations.getProductsByRating({ rating: 5 })

// V4 Function (no side effects)
const airport = yield* Operations.getNearestAirport({ lat: 51.5, lon: -0.1 })

// V4 Action (may have side effects)
yield* Operations.resetDataSource()

License

MIT