A utility library that bridges Effect-ts with various utilities and projects, such as Astro!
- 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
pnpm add effectifyUndocumented Modules:
effectify/scalar- Custom version of the Scalar provided by Effect's HttpApieffectify/scrypt- Node Scrypt Effect wrapper for password hashing
Utilities specific for working with Effect and Astro
This module provides an AstroAPIContext export that can be used when working with Effect HTTP web handlers within an Astro environment.
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!';
})
)
);This module provides a Utility to wrap your Effect-based HttpApi into a Astro APIRoute
// 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);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.
- 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
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,
}),
],
});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,
})
)
)
),
}),
});These functions provide the ability to define custom functions within Effect Schema for both async and sync functions.
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
*/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
*/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')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');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);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);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.
- 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
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',
});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);- 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.
Contributions are welcome! Please follow the StudioCMS contribution guidelines.