Skip to content

Repository files navigation

Effect-Services

A collection of various Effectful services to communicate with 3rd party services, as well as utilities.

The modules included are used for my specific use-cases and so may be opionated, but I have attempted to make them as generic as possible nevertheless.

npm versionnpm downloadsnpm bundle sizePublish to NPM

Installation

bun add effect-services effect
# or
npm install effect-services effect
# or
yarn add effect-services effect

Note:effect is a peer dependency and must be installed separately. Check the Peer Dependencies in package.json to see which effect version is supported.

Modules

Azure Cosmos NoSQL

Effectful wrapper for Azure Cosmos DB NoSQL operations. Provides a type-safe interface for database and container operations with streaming support.

import*asCosmosfrom"effect-services/cosmos";

Features:

  • Effectful container SDK wrapper for CRUD operations
  • Custom client implementation including rate limiting handling
  • Query to Effect Stream support

Companies House

Integration with the UK Companies House API for business information retrieval.

import*asCompaniesHousefrom"effect-services/companieshouse";

Features:

  • Response types provided from underlying Companies House SDK.
  • Rate limit handling in CompaniesHouse

FreshService

Integration with Freshservice ticketing and IT service management platform.

import*asFreshServicefrom"effect-services/freshservice";

Features:

  • Authentication handling
  • Rate limit handling
  • Automatic API pagination via Effect Stream

iManage

Integration with iManage document and content management system.

import*asiManagefrom"effect-services/imanage";

Features:

  • Automatic OAuth2 token refreshing
  • File upload helper function

Azure Key Vault

Secure secrets management using Azure Key Vault.

import*asKeyVaultfrom"effect-services/keyvault";

Features:

  • Secrets retrieval with optional caching
  • No bootstrap secrets required
  • Can be used as an Effect ConfigProvider

Legl

Integration with Legl automation platform.

import*asLeglfrom"effect-services/legl";

Features:

  • Automatic API pagination via Effect Stream
  • Comprehensive Legl API Schemas

Microsoft Graph

Comprehensive wrapper for Microsoft Graph API with streaming capabilities.

import*asGraphfrom"effect-services/msgraph";

Features:

  • Effectful wrapper for query construction
  • Support for Graph Query to Effect Stream

MSSQL

Effectful MSSQL database client with query pooling and streaming.

import*asMSSQLfrom"effect-services/mssql";

Features:

  • Effectful connection pooling
  • SQL query to Effect Stream support
  • Effectful wrapper for the mssql SDK

Utils

Utility functions and helpers for Effect-based applications.

import*asUtilsfrom"effect-services/utils";

Features:

  • Task scheduling for development environments via Crons and CLI
  • IP Address and CIDR parsing
  • Dynamic bloom filters
  • Math functions

Azure File Share

Integration with Azure Storage File Shares.

import*asAzureFSfrom"effect-services/azurefs";

Features:

  • File and directory operations
  • Directory items to Effect Stream helper

Import Guide

Important! There is no single entrypoint for this repo. When importing, use namespace imports:

import*asLeglfrom"effect-services/legl";import*asGraphfrom"effect-services/msgraph";import*asMSSQLfrom"effect-services/mssql";

Not:

importLeglfrom"effect-services";// ❌ This won't work

Examples

All public APIs include JsDoc examples (WIP). Refer to the relevant function documentation for detailed usage patterns.

Microsoft Graph Example

Interact with Microsoft 365 services including users, mail, and OneDrive:

import*asGraphfrom"effect-services/msgraph";import{Effect,Stream}from"effect";constExampleListUsers=Effect.gen(function*(){constgraph=yield*Graph.MsGraph;constrequest=yield*graph.use((c)=>c.api("/users").select(["id","displayName","mail"]).top(10));conststream=yield*Graph.MakeStream(request);yield*Stream.runForEach(stream,(user)=>Effect.log(`User: ${user.displayName} (${user.mail})`));});constLayerLive=Graph.layer({tenantID: "your-tenant-id",clientID: "your-client-id",clientSecret: "your-client-secret",scopes: ["https://graph.microsoft.com/.default"]});ExampleListUsers.pipe(Effect.provide(LayerLive),Effect.runPromise);

MSSQL Example

Execute queries and handle streaming results with transaction support:

import*asMSSQLfrom"effect-services/mssql";import{Effect,Stream}from"effect";constQueryExample=Effect.gen(function*(){constsql=yield*MSSQL.MsSqlClient;// Access the connection poolconstpool=yield*sql.MakePool;constqueryString="SELECT * FROM Users WHERE Active = 1";// Simple queryconstresult=yield*Effect.tryPromise(()=>pool.query(queryString));yield*Effect.log("result:",result.output);// Streaming for large datasetsconststream=yield*sql.MakeStream(queryString);yield*Stream.runForEach(stream,(r)=>Effect.log("record:",r));});constLayerLive=MSSQL.MsSqlClient.layer({server: "your-server.database.windows.net",database: "your-database",user: "username",password: "password",// ...});QueryExample.pipe(Effect.provide(LayerLive),Effect.runPromise);

Azure Cosmos DB Example

Work with document collections using the Cosmos SDK:

import*asCosmosfrom"effect-services/cosmos";import{Effect,Stream}from"effect";constCosmosExample=Effect.gen(function*(){constcosmos=yield*Cosmos.CosmosClient;constcontainer=yield*cosmos.container("Users");// Container operationsconstupdateUser=yield*container.upsert({id: "123",status: "inactive",});yield*Effect.log("new status:",updateUser.resource?.status);{// Stream all items in containerconststream=yield*container.allItems;yield*Stream.runForEach(stream,(r)=>Effect.log("record ID:",r.id));}{// Stream items from queryconststream=yield*container.queryToStream({query: "SELECT * FROM c WHERE c.status = @status",parameters: [{name: "@status",value: "active"}]});yield*Stream.runForEach(stream,(u)=>Effect.log("user:",u));}// Access underlying clientsconsturl=yield*Effect.tryPromise(()=>cosmos.client.getReadEndpoint());constresponse=yield*Effect.tryPromise(()=>cosmos.database.read());});constLayerLive=Cosmos.CosmosClient.layer({connectionString: "https://your-account.documents.azure.com:443/",databaseID: "your-database-name"});CosmosExample.pipe(Effect.provide(LayerLive),Effect.runPromise);

Azure Key Vault Example

Securely retrieve and manage secrets:

import*asKeyVaultfrom"effect-services/keyvault";import{Effect,Layer}from"effect";import{DefaultAzureCredential}from"@azure/identity";constSecretExample=Effect.gen(function*(){constvault=yield*KeyVault.KeyVault;// Retrieve a secretconstdbPassword=yield*vault.getSecret("database-password");// Retrieve with cacheconstcachedVault=yield*KeyVault.KeyVaultAsCache;constapiKey=yield*cachedVault.lookup("api-key");// Use in applicationconstconnectionString=`Server=myserver;Password=${dbPassword};`;yield*Effect.log(`Connected with key version: ${apiKey.version}`);});constKeyVaultLayer=KeyVault.KeyVault.layer({vaultURL: "https://your-vault.vault.azure.us/",credential: newDefaultAzureCredential()});constCacheLayer=KeyVault.KeyVaultAsCache.layer({capacity: 10,timeToLive: "1 hour",});constLiveLayer=Layer.merge(KeyVaultLayer,CacheLayer);SecretExample.pipe(Effect.provide(LiveLayer),Effect.runPromise);

Companies House Example

Retrieve UK business information:

import*asCompaniesHousefrom"effect-services/companieshouse";import{Effect}from"effect";constCompanyLookup=Effect.gen(function*(){constclient=yield*CompaniesHouse.CompaniesHouse;constprofile=yield*client.use((c)=>c.companyProfile.getCompanyProfile("07424016")).pipe(Effect.map((r)=>r.resource));yield*Effect.log(profile?.companyName);yield*Effect.log(profile?.accounts.nextAccounts);});constLayerLive=CompaniesHouse.CompaniesHouse.layer({apiKey: "your-api-key"});CompanyLookup.pipe(Effect.provide(LayerLive),Effect.runPromise);

Requirements

  • Bun, Node.js or equivelent runtimes.
  • For versions ^2.0.0: Effect ^3.0.0 (installed separately as peer dependency)
  • For versions ^3.0.0: Effect ^4.0.0 (installed separately as peer dependency)
  • All other module requirements are bundled as dependencies (e.g. @azure/identity)

Best Practices

Error Handling

All modules which use custom implementations of services will have Effectful, tagged errors:

import{Effect}from"effect";import*asMsGraphfrom"effect-services/msgraph";constMain=Effect.gen(function*(){constgraph=yield*MsGraph.MsGraph;// Effect<GraphRequest, MsGraph.MsGraphError>constquery=graph.use((c)=>c.api("/invalid/path"));});

Streaming Large Datasets

Use streaming for memory-efficient processing of large result sets:

import*asGraphfrom"effect-services/msgraph";import{Stream,Effect}from"effect";// Good: Memory-efficient streamingconstStreamUsers=Effect.gen(function*(){constgraph=yield*Graph.MsGraph;constrequest=yield*graph.use((c)=>c.api("/users"));conststream=yield*Graph.MakeStream(request);yield*Stream.runForEach(stream,(user)=>Effect.log(user));});

Development

Setup

Bun is required for development:

# Clone and install dependencies
git clone https://github.com/Shiv-SB/effect-services
cd effect-services
bun i

Building

# Build TypeScript and generate type definitions
bun run build
# This will:# - Transpile .ts to .js# - Generate .d.ts type files# - Validate exports in package.json

Workspace Structure

src/ # Source TypeScript files
├── cosmos/ # Azure Cosmos DB module
├── msgraph/ # Microsoft Graph module
├── mssql/ # MSSQL database module
├── keyvault/ # Azure Key Vault module
└── ... # Other service modules
build/ # Compiled JavaScript and type definitions
lib/ # Build utilities and helpers
tests/ # Unit and integration tests

Development Tips

  • Try to keep each module independent. Any shared functions should be stored in internals.
  • The package exports are validated on build to ensure correctness

Common Patterns

Dependency Injection

All modules use Effect's dependency injection for configuration:

import*asMSSQLfrom"effect-services/mssql";import{Effect}from"effect";constMyApp=Effect.gen(function*(){constclient=yield*MSSQL.MsSqlClient;// Use client});constlayer=MSSQL.MsSqlClient.layer({/* config */});MyApp.pipe(Effect.provide(layer),Effect.runPromise);

Combining Multiple Services

This example showcase using three different services concurrently, mssql, msgraoh and keyvault. msgraph and mssql are used to generate Streams, and keyvault is used as the ConfigProvider.

import{DefaultAzureCredential}from"@azure/identity";import{Config,ConfigProvider,Effect,Layer,Stream}from"effect";import{MakeKeyVaultProvider}from"effect-services/keyvault";import*asMsGraphfrom"effect-services/msgraph";import*asSQLfrom"effect-services/mssql";constGetGraphUsers=Effect.gen(function*(){constgraph=yield*MsGraph.MsGraph;// .use method to access the api builderconstquery=yield*graph.use((c)=>c.api("/users").select(["id","displayName","officeLocation"]));// Pass the query to the Stream factoryconststream=yield*MsGraph.MakeStream(query);returnstream;});constGetSqlUsers=Effect.gen(function*(){constsql=yield*SQL.MsSqlClient;conststream=yield*sql.MakeStream("SELECT * FROM Users");returnstream;});constExampleApp=Effect.all([GetGraphUsers,GetSqlUsers]).pipe(Effect.map(([s1,s2])=>Stream.merge(s1,s2)),Effect.tap(Effect.log("Starting...")),Effect.andThen(Stream.runForEach((user)=>Effect.log("user:",user))),Effect.tap(Effect.log("Finished!")),)// Create a ConfigProvider backed by Azure Key Vault// No secrets needed! Authentication happens via the credential,// so your codebase can be completely free of secrets and keys.constSecretsProvider=MakeKeyVaultProvider({vaultURL: "https://my-vault.vault.azure.us/",credential: newDefaultAzureCredential()});// Provide a fallback to process.envconstMergedProvider=SercretsProvider.pipe(ConfigProvider.orElse(ConfigProvider.fromEnv));// Convert the provider into a layer.constproviderLayer=ConfigProvider.layer(MergedProvider);// A simple provider function which constructs and then provides our layers.// We use an Effectful function for this because we need to yield* Config values.constProvideLayers=<A,E,R>(runnable: Effect.Effect<A,E,R>)=>Effect.gen(function*(){// Construct each layer...constgraphLayer=MsGraph.MsGraph.layer({clientID: yield*Config.string("example-secret-clientID"),clientSecret: yield*Config.string("example-secret-clientSecret"),tenantID: yield*Config.string("example-secret-tenantID"),scopes: ["https://graph.microsoft.com/.default"]});constsqlLayer=SQL.MsSqlClient.layer({server: yield*Config.string("example-secret-server"),password: yield*Config.string("example-secret-password"),port: yield*Config.port("server-port").pipe(Config.withDefault(1433))});// Combine all your layers!constallLayers=Layer.mergeAll(graphLayer,sqlLayer);returnyield*runnable.pipe(Effect.provide(allLayers));}).pipe(Effect.provide(providerLayer));ExampleApp.pipe(ProvideLayers,Effect.runPromise,);

Contributing

Contributions are welcome! Please:

  1. Fork the repository and create a feature branch
  2. Follow the existing patterns - Each module should follow the same structure and conventions
  3. Write tests for new functionality
  4. Build and validate with bun run build & bun audit before submitting
  5. Keep Effect v4 compatibility unless there's a major version bump

Module Development Guidelines

  • Use Effect for all async operations.
  • Do not create any side-effects.
  • Provide JSDoc comments with usage examples
  • Include type definitions (even when verbose to do so)
  • Create corresponding tests
  • Update the README with module description

License

See LICENSE file for details.

Support

For issues, questions, or feature requests, please open an issue on GitHub at Shiv-SB/effect-services

About

Useful Effect services

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages