effectify #
A utility library that bridges Effect-ts with various utilities and projects, such as Astro!
Features #
- Effect-ts Integration - Convert Effect HTTP APIs to Astro API routes
- Context Management - Seamless Astro context integration with Effect contexts
- Web Handler Conversion - Built-in conversion utilities for Effect HTTP handlers
- Scalar API Documentation - Integrated API documentation generation
Installation #
pnpm add effectify
Usage #
Undocumented Modules:
effectify/scalar- Custom version of the Scalar provided by Effect's HttpApieffectify/scrypt- Node Scrypt Effect wrapper for password hashing
effectify/astro/* Modules #
Utilities specific for working with Effect and Astro
effectify/astro/context #
This module provides an AstroAPIContext export that can be used when working with Effect HTTP web handlers within an Astro environment.
Example #
The example below shows how to access Astro locals from within en HttpApi handler
import { AstroAPIContext } from 'effectify/astro/context';
// Example of defining an Effect API and converting it to an Astro API route handler
const api = HttpApi.make('myApi').add(
HttpApiGroup.make('group').add(HttpApiEndpoint.get('get', '/').addSuccess(Schema.String))
);
// Build the Effect API Handler (with Astro context)
const groupLive = HttpApiBuilder.group(api, 'group', (handlers) =>
handlers.handle('get', () =>
Effect.gen(function* () {
const { locals } = yield* AstroAPIContext;
console.log('Astro Locals:', locals); // Log the locals to verify access
return 'Hello from Effect API!';
})
)
);
effectify/astro/HttApi #
This module provides a Utility to wrap your Effect-based HttpApi into a Astro APIRoute
Example #
// src/pages/myApi.ts
import { HttpApiToAstroRoute } from 'effectify/astro/HttApi';
// Example of defining an Effect API and converting it to an Astro API route handler
const api = HttpApi.make('myApi').add(
HttpApiGroup.make('group').add(HttpApiEndpoint.get('get', '/').addSuccess(Schema.String))
);
// Build the Effect API Handler (with Astro context)
const groupLive = HttpApiBuilder.group(api, 'group', (handlers) =>
handlers.handle('get', () =>
Effect.gen(function* () {
const { locals } = yield* AstroAPIContext;
console.log('Astro Locals:', locals); // Log the locals to verify access
return 'Hello from Effect API!';
})
)
);
// Create the Live Layer for the API
const MyApiLive = HttpApiBuilder.api(api).pipe(Layer.provide(groupLive));
// Convert the Effect API to an Astro API route handler
const ALL = HttpApiToAstroRoute(MyApiLive);
effectify/astro/integration #
This module provides a defineIntegration utility for building Astro integrations with Effect-ts. The helper adds schema-validation for integration options and automatically wraps integration hooks to surface errors as EffectifyIntegrationHookError instances.
Features #
- Schema Validation - Validate integration options using Effect Schema at initialization time
- Automatic Error Handling - All Effect-based hooks are wrapped to catch and re-throw errors with proper context
- Type Safety - Full TypeScript support with inferred types from schemas
- Effect Integration - Native Effect-ts patterns for hooks
Example #
import { Effect, Schema } from 'effect';
import { defineIntegration, EffectifyIntegrationHookError } from 'effectify/astro/integration';
// Define an Astro integration with schema-validated options
export const myIntegration = defineIntegration({
name: 'my-integration',
schema: Schema.Struct({
apiKey: Schema.String,
enabled: Schema.optionalWith(Schema.Boolean, {
default: () => true,
}),
}),
setup: ({ name, options }) => ({
'astro:config:setup': ({ logger }) =>
Effect.gen(function* () {
if (options.enabled) {
logger.info(`${name} is enabled`);
}
}),
'astro:config:done': ({ logger }) =>
Effect.gen(function* () {
logger.info(`Configuration complete for ${name}`);
}),
}),
});
// Use in astro.config.mjs
export default defineConfig({
integrations: [
myIntegration({
apiKey: 'my-secret-key',
enabled: true,
}),
],
});
Error Handling #
The defineIntegration utility automatically wraps all hooks to catch errors. You can also explicitly handle errors within your hooks:
export const myIntegration = defineIntegration({
name: 'my-integration',
setup: ({ name, options }) => ({
'astro:config:setup': ({ logger }) =>
Effect.gen(function* () {
// Your integration logic here
}).pipe(
Effect.catchAll((error) =>
Effect.fail(
new EffectifyIntegrationHookError({
hook: 'astro:config:setup',
message: 'Failed to setup integration',
cause: error,
})
)
)
),
}),
});
effectify/schemas #
FunctionSchema / SyncFunctionSchema #
These functions provide the ability to define custom functions within Effect Schema for both async and sync functions.
Example (FunctionSchema) #
Creates a schema for functions with validated inputs and outputs. Similar to Zod's z.function().
The first param, is the args input, the second is the return. FunctionSchema will always return a async function. (promise-based)
import { FunctionSchema } from 'effectify/schemas';
import { Schema } from 'effect';
// Define your Schema
const LoginSchema = FunctionSchema(
Schema.Struct({ username: Schema.String, password: Schema.String }),
Schema.Boolean
);
// Example implementation function
const rawLoginFn = async (data: { username: string; password: string }) => {
return data.username === 'admin' && data.password === '123';
};
// Create decoder
const decoder = Schema.decodeSync(LoginSchema)(rawLoginFn);
// Verify using implementation function internally
const result = await decoder({ username: 'admin', password: '123' });
console.log(result)
/* Console Output:
true
*/
Example (SyncFunctionSchema) #
Similar to FunctionSchema but will always return a synchronous function.
import { SyncFunctionSchema } from 'effectify/schemas';
import { Schema } from 'effect';
// Define your Schema
const LoginSchema = SyncFunctionSchema(
Schema.Struct({ username: Schema.String, password: Schema.String }),
Schema.Boolean
);
// Example implementation function
const rawLoginFn = (data: { username: string; password: string }) => {
return data.username === 'admin' && data.password === '123';
};
// Create decoder
const decoder = Schema.decodeSync(LoginSchema)(rawLoginFn);
// Verify using implementation function internally
const result = decoder({ username: 'admin', password: '123' });
console.log(result)
/* Console Output:
true
*/
effectify/static #
makeStaticFileHttpApiRouter #
Creates an HTTP API router that serves static files from a specified directory. The router can be configured to serve an index.html file for the root path and to set appropriate caching headers for files with hashed filenames.
// /src/index.ts
import { makeStaticFileHttpApiRouter } from 'effectify/static';
const TestStatic = makeStaticFileHttpApiRouter({
htmlIndex: true, // (optional) Allows for serving index.html
pathPrefix: '/assets' // (optional) Allows setting custom base path for served assets (leave blank for base route)
})(import.meta.dirname, 'static'); // Directory would be /src/static/
// If the source directory was /src/index.ts and the assets was located at /assets/
// then you would use (import.meta.dirname, '..', 'assets')
makeStaticFileMiddleware #
The router above is simply a wrapper around this middleware, and has the same settings.
import { makeStaticFileMiddleware } from 'effectify/static';
const StaticMiddleware = makeStaticFileMiddleware()(import.meta.dirname, 'static');
effectify/webHandler #
webHandlerToEffectHttpHandler #
Converts a web handler function to an Effect HttpServerRequest handler.
import { webHandlerToEffectHttpHandler } from 'effectify/webHandler';
const mockWebHandler = async (request: Request) => {
console.log('Received request:', request);
return new Response('Hello from mock web handler!', { status: 200 });
};
const EffectHttpHandler = webHandlerToEffectHttpHandler(mockWebHandler);
EffectHttpHandlerToHttpApi #
Utility function to convert a Effect-based web handler into a format that can be used with the HttpApiBuilder from Effect. This allows you to define your web handlers using Effect and then easily integrate them into an HTTP API.
// src/pages/[...api].ts
import { webHandlerToEffectHttpHandler, EffectHttpHandlerToHttpApi } from 'effectify/webHandler';
const mockWebHandler = async (request: Request) => {
console.log('Received request:', request);
return new Response('Hello from mock web handler!', { status: 200 });
};
const EffectHttpHandler = webHandlerToEffectHttpHandler(mockWebHandler);
const EffectHttpApiHandler = EffectHttpHandlerToHttpApi('*', EffectHttpHandler);
// Using a previous example to demonstrate possible usage
const ALL = HttpApiToAstroRoute(EffectHttpApiHandler);
effectify/zod #
Converts Zod schemas to Effect schemas, enabling seamless migration from Zod to Effect's type-safe schema system. This function supports most common Zod schema types including primitives, objects, arrays, unions, intersections, and more.
Features #
- Comprehensive Type Support - Handles primitives (string, number, boolean, etc.), complex types (objects, arrays, tuples), and special types (dates, symbols, bigints)
- File Schema Support - Includes
FileFromSelfschema for handling JavaScript File objects - String Representation - Provides
zodToEffect.writeable()for debugging and visualization of the generated Effect schema structure
Basic Example #
import { zodToEffect } from 'effectify/zod';
import { z } from 'zod';
// Define a Zod schema
const userSchema = z.object({
username: z.string(),
age: z.number().int(),
email: z.string().optional(),
});
// Convert to Effect schema
const effectUserSchema = zodToEffect(userSchema);
// Use the Effect schema for validation
const result = Schema.decodeUnknownSync(effectUserSchema)({
username: 'johndoe',
age: 25,
email: 'john@example.com',
});
Complex Example #
import { zodToEffect } from 'effectify/zod';
import { z } from 'zod';
// Complex nested schema with unions and intersections
const complexSchema = z.union([
z.object({
type: z.literal('user'),
data: z.tuple([
z.string(),
z.object({
id: z.int().optional(),
tags: z.array(z.string()),
}),
]),
}),
z.intersection(
z.object({
type: z.literal('admin'),
permissions: z.record(z.string(), z.array(z.string())),
}),
z.object({
createdAt: z.date(),
})
),
]);
// Convert to Effect schema
const effectSchema = zodToEffect(complexSchema);
// For debugging, visualize the schema structure as a string
const schemaString = zodToEffect.writeable(complexSchema);
console.log(schemaString);
Supported Schema Types #
- Primitives:
string,number,int,bigint,boolean,date,symbol - Special values:
null,undefined,void,any,unknown,never,nan - Collections:
array,set,map,record,tuple - Composition:
union,intersection,object(struct) - Modifiers:
optional,nullable,readonly,nonoptional - Other:
literal,enum,template_literal,file
Note: Some Zod schema types like
lazy,transform,pipe,promise, andcustomare not currently supported and will throw an error.
Contributing #
Contributions are welcome! Please follow the StudioCMS contribution guidelines.