| name | effect-domain-predicates |
| description | Generate comprehensive predicates and orders for domain types using typeclass patterns |
Domain Predicates Skill
Generate complete sets of predicates and Order instances for domain types, derived from typeclass implementations.
Effect Source Reference
The Effect v4 source is available at ~/.cache/effect-v4/.
Browse and read files there directly to look up APIs, types, and implementations.
Reference this for:
- Full Schema API:
packages/effect/SCHEMA.md
- Order source:
packages/effect/src/Order.ts
- Equivalence source:
packages/effect/src/Equivalence.ts
- Predicate source:
packages/effect/src/Predicate.ts
- Effect source:
packages/effect/src/
Pattern: Deep Structural Equality (v4)
In v4, Equal.equals performs deep structural comparison by default — no special wrapping is needed:
import { Schema, Equal, DateTime } from 'effect';
export const Task = Schema.TaggedStruct('pending', {
id: Schema.String,
createdAt: Schema.DateTimeUtc
});
export type Task = Schema.Schema.Type<typeof Task>;
declare const makeTask: (props: {
id: string;
createdAt: DateTime.Utc;
}) => Task;
declare const now: DateTime.Utc;
const task1 = makeTask({ id: '123', createdAt: now });
const task2 = makeTask({ id: '123', createdAt: now });
Equal.equals(task1, task2);
Pattern: Equivalence from Schema
When you need an Equivalence instance (for use with combinators), derive it from the schema:
import { Schema, Array } from 'effect';
import * as Equivalence from 'effect/Equivalence';
declare const Task: Schema.Schema<any, any, never>;
type Task = Schema.Schema.Type<typeof Task>;
export const TaskEquivalence = Schema.toEquivalence(Task);
declare const tasks: Array<Task>;
const uniqueTasks = Array.dedupeWith(tasks, TaskEquivalence);
Pattern: Field-Based Equivalence with Equivalence.mapInput
Compare by specific fields using Equivalence.mapInput:
import { DateTime } from 'effect';
import * as Equivalence from 'effect/Equivalence';
interface Task {
readonly _tag: string;
readonly id: string;
readonly createdAt: DateTime.Utc;
}
export const EquivalenceById = Equivalence.mapInput(
Equivalence.String,
(task: Task) => task.id
);
export const EquivalenceByTag = Equivalence.mapInput(
Equivalence.String,
(task: Task) => task._tag
);
export const EquivalenceByCreatedAt = Equivalence.mapInput(
DateTime.Equivalence,
(task: Task) => task.createdAt
);
Key Pattern: Equivalence.mapInput
- Signature:
Equivalence.mapInput(baseEquivalence, (value) => extractField)
- Compose from simpler equivalences
- Map domain type to comparable value
- Dual API: data-first and data-last
Pattern: Combining Equivalences
Use Equivalence.combine for two-field equality and Equivalence.combineAll for three or more fields:
import { DateTime } from 'effect';
import * as Equivalence from 'effect/Equivalence';
interface Task {
readonly _tag: string;
readonly id: string;
readonly createdAt: DateTime.Utc;
}
declare const EquivalenceByTag: Equivalence.Equivalence<Task>;
declare const EquivalenceById: Equivalence.Equivalence<Task>;
declare const EquivalenceByCreatedAt: Equivalence.Equivalence<Task>;
export const EquivalenceByTagAndId = Equivalence.combine(
EquivalenceByTag,
EquivalenceById
);
export const EquivalenceComplete = Equivalence.combineAll([
EquivalenceByTag,
EquivalenceById,
EquivalenceByCreatedAt
]);
Key Pattern: Equivalence.combine / combineAll
Equivalence.combine combines two equivalences
- Use
Equivalence.combineAll([...]) for three or more equivalences
- All must match for equivalence (AND logic)
- Order doesn't matter (unlike Order.combine)
Pattern: Typeclass-Derived Predicates
When a domain type implements a typeclass, re-export all relevant predicates:
import { DateTime, Duration, Schema } from 'effect';
import * as Order from 'effect/Order';
interface SchedulableInstance<A> {
readonly get: (self: A) => DateTime.DateTime;
readonly set: (self: A, date: DateTime.DateTime) => A;
}
interface DurableInstance<A> {
readonly get: (self: A) => Duration.Duration;
readonly set: (self: A, duration: Duration.Duration) => A;
}
declare const Schedulable$: {
make: <A>(
get: (self: A) => DateTime.DateTime,
set: (self: A, date: DateTime.DateTime) => A
) => SchedulableInstance<A>;
isScheduledBefore: <A>(
instance: SchedulableInstance<A>
) => (date: DateTime.DateTime) => (self: A) => boolean;
isScheduledAfter: <A>(
instance: SchedulableInstance<A>
) => (date: DateTime.DateTime) => (self: A) => boolean;
isScheduledBetween: <A>(
instance: SchedulableInstance<A>
) => (
start: DateTime.DateTime,
end: DateTime.DateTime
) => (self: A) => boolean;
isScheduledOn: <A>(
instance: SchedulableInstance<A>
) => (date: DateTime.DateTime) => (self: A) => boolean;
isScheduledToday: <A>(
instance: SchedulableInstance<A>
) => (self: A) => boolean;
isScheduledThisWeek: <A>(
instance: SchedulableInstance<A>
) => (self: A) => boolean;
isScheduledThisMonth: <A>(
instance: SchedulableInstance<A>
) => (self: A) => boolean;
};
declare const Durable$: {
make: <A>(
get: (self: A) => Duration.Duration,
set: (self: A, duration: Duration.Duration) => A
) => DurableInstance<A>;
isMoreThan: <A>(
instance: DurableInstance<A>
) => (min: Duration.Duration) => (self: A) => boolean;
isLessThan: <A>(
instance: DurableInstance<A>
) => (max: Duration.Duration) => (self: A) => boolean;
isBetween: <A>(
instance: DurableInstance<A>
) => (
min: Duration.Duration,
max: Duration.Duration
) => (self: A) => boolean;
hasExactDuration: <A>(
instance: DurableInstance<A>
) => (duration: Duration.Duration) => (self: A) => boolean;
};
interface Appointment {
readonly date: DateTime.Utc;
readonly duration: Duration.Duration;
}
declare const Appointment: {
make: (props: Partial<Appointment>) => Appointment;
};
export const Schedulable = Schedulable$.make<Appointment>(
(self: Appointment) => self.date,
(self: Appointment, date: DateTime.DateTime) =>
Appointment.make({ ...self, date: DateTime.toUtc(date) })
);
export const Durable = Durable$.make<Appointment>(
(self: Appointment) => self.duration,
(self: Appointment, duration: Duration.Duration) =>
Appointment.make({ ...self, duration })
);
export const isScheduledBefore = Schedulable$.isScheduledBefore(Schedulable);
export const isScheduledAfter = Schedulable$.isScheduledAfter(Schedulable);
export const isScheduledBetween = Schedulable$.isScheduledBetween(Schedulable);
export const isScheduledOn = Schedulable$.isScheduledOn(Schedulable);
export const isScheduledToday = Schedulable$.isScheduledToday(Schedulable);
export const isScheduledThisWeek =
Schedulable$.isScheduledThisWeek(Schedulable);
export const isScheduledThisMonth =
Schedulable$.isScheduledThisMonth(Schedulable);
export const hasMinimumDuration = Durable$.isMoreThan(Durable);
export const hasMaximumDuration = Durable$.isLessThan(Durable);
export const hasDurationBetween = Durable$.isBetween(Durable);
export const hasExactDuration = Durable$.hasExactDuration(Durable);
Pattern: Order Instances with Order.mapInput
Compose orders from simpler base orders using Order.mapInput:
import { DateTime } from 'effect';
import * as Order from 'effect/Order';
import * as String from 'effect/String';
interface Task {
readonly _tag: 'pending' | 'active' | 'completed';
readonly id: string;
readonly createdAt: DateTime.Utc;
}
export const OrderById: Order.Order<Task> = Order.mapInput(
Order.String,
(task: Task) => task.id
);
export const OrderByCreatedAt: Order.Order<Task> = Order.mapInput(
DateTime.Order,
(task: Task) => task.createdAt
);
export const OrderByTag: Order.Order<Task> = Order.mapInput(
Order.String,
(task: Task) => task._tag
);
export const OrderByPriority: Order.Order<Task> = Order.mapInput(
Order.Number,
(task: Task) => {
const priorities = { pending: 0, active: 1, completed: 2 };
return priorities[task._tag];
}
);
Key Pattern: Order.mapInput
- Signature:
Order.mapInput(baseOrder, (value) => extractField)
- Compose from existing orders (Order.String, Order.Number, DateTime.Order, etc.)
- Map domain type to comparable value
- Dual API: data-first and data-last
Pattern: Combining Orders with Order.combine
Use Order.combine for multi-criteria sorting:
import { DateTime } from 'effect';
import * as Order from 'effect/Order';
interface Task {
readonly _tag: 'pending' | 'active' | 'completed';
readonly id: string;
readonly createdAt: DateTime.Utc;
}
declare const OrderByPriority: Order.Order<Task>;
declare const OrderByCreatedAt: Order.Order<Task>;
declare const OrderByTag: Order.Order<Task>;
declare const OrderById: Order.Order<Task>;
export const OrderByPriorityThenDate: Order.Order<Task> = Order.combine(
OrderByPriority,
OrderByCreatedAt
);
export const OrderComplex: Order.Order<Task> = Order.combineAll([
OrderByTag,
OrderById,
OrderByCreatedAt
]);
Key Pattern: Order.combine / combineAll
Order.combine combines two orders for multi-criteria sorting
- Use
Order.combineAll([...]) for three or more orders
- First order takes precedence, then second, etc.
- Order matters (unlike Equivalence.combine)
- Returns combined order that can be used with Array.sort
Pattern: Comprehensive Order Instances
Provide extensive sorting capabilities:
import { DateTime, Duration } from 'effect';
import * as Order from 'effect/Order';
import * as String from 'effect/String';
interface SchedulableInstance<A> {
readonly get: (self: A) => DateTime.DateTime;
readonly set: (self: A, date: DateTime.DateTime) => A;
}
interface DurableInstance<A> {
readonly get: (self: A) => Duration.Duration;
readonly set: (self: A, duration: Duration.Duration) => A;
}
declare const Schedulable$: {
OrderByScheduledTime: <A>(
instance: SchedulableInstance<A>
) => Order.Order<A>;
OrderByDayOfWeek: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByTimeOfDay: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByHour: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByMonth: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByYear: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByYearMonth: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByDateOnly: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByDayPeriod: <A>(instance: SchedulableInstance<A>) => Order.Order<A>;
OrderByBusinessHours: <A>(
instance: SchedulableInstance<A>
) => Order.Order<A>;
OrderByWeekdayFirst: <A>(
instance: SchedulableInstance<A>
) => Order.Order<A>;
};
declare const Durable$: {
OrderByDuration: <A>(instance: DurableInstance<A>) => Order.Order<A>;
OrderByHours: <A>(instance: DurableInstance<A>) => Order.Order<A>;
OrderByMinutes: <A>(instance: DurableInstance<A>) => Order.Order<A>;
OrderBySeconds: <A>(instance: DurableInstance<A>) => Order.Order<A>;
};
type AppointmentStatus = 'scheduled' | 'confirmed' | 'completed' | 'cancelled';
interface Appointment {
readonly date: DateTime.Utc;
readonly duration: Duration.Duration;
readonly status: AppointmentStatus;
}
declare const Schedulable: SchedulableInstance<Appointment>;
declare const Durable: DurableInstance<Appointment>;
export const OrderByScheduledTime =
Schedulable$.OrderByScheduledTime(Schedulable);
export const OrderByDayOfWeek = Schedulable$.OrderByDayOfWeek(Schedulable);
export const OrderByTimeOfDay = Schedulable$.OrderByTimeOfDay(Schedulable);
export const OrderByHour = Schedulable$.OrderByHour(Schedulable);
export const OrderByMonth = Schedulable$.OrderByMonth(Schedulable);
export const OrderByYear = Schedulable$.OrderByYear(Schedulable);
export const OrderByYearMonth = Schedulable$.OrderByYearMonth(Schedulable);
export const OrderByDateOnly = Schedulable$.OrderByDateOnly(Schedulable);
export const OrderByDayPeriod = Schedulable$.OrderByDayPeriod(Schedulable);
export const OrderByBusinessHours =
Schedulable$.OrderByBusinessHours(Schedulable);
export const OrderByWeekdayFirst =
Schedulable$.OrderByWeekdayFirst(Schedulable);
export const OrderByDuration = Durable$.OrderByDuration(Durable);
export const OrderByHours = Durable$.OrderByHours(Durable);
export const OrderByMinutes = Durable$.OrderByMinutes(Durable);
export const OrderBySeconds = Durable$.OrderBySeconds(Durable);
export const OrderByStatus: Order.Order<Appointment> = Order.mapInput(
String.Order,
(appt: Appointment) => appt.status
);
export const OrderByStatusPriority: Order.Order<Appointment> = Order.mapInput(
Order.Number,
(appt: Appointment) => {
const priorities: Record<AppointmentStatus, number> = {
scheduled: 0,
confirmed: 1,
completed: 2,
cancelled: 3
};
return priorities[appt.status];
}
);
export const OrderByStatusThenTime: Order.Order<Appointment> = Order.combine(
OrderByStatusPriority,
OrderByScheduledTime
);
Usage Examples
Equality Examples
import { Equal, Array } from 'effect';
import * as Equivalence from 'effect/Equivalence';
declare module '@/schemas/Task' {
export interface Task {
readonly _tag: string;
readonly id: string;
}
export const EquivalenceById: Equivalence.Equivalence<Task>;
export const EquivalenceByTagAndId: Equivalence.Equivalence<Task>;
}
import * as Task from '@/schemas/Task';
declare const task1: Task.Task;
declare const task2: Task.Task;
declare const tasks: Array<Task.Task>;
declare const searchTask: Task.Task;
const areSame = Equal.equals(task1, task2);
const uniqueById = Array.dedupeWith(tasks, Task.EquivalenceById);
const uniqueByTagAndId = Array.dedupeWith(tasks, Task.EquivalenceByTagAndId);
const hasTask = Array.containsWith(tasks, Task.EquivalenceById)(searchTask);
Filtering Examples
Document how these predicates enable powerful filtering:
import { DateTime, Duration, Array } from 'effect';
import { pipe } from 'effect/Function';
declare module '@/schemas/Appointment' {
export interface Appointment {
readonly date: DateTime.Utc;
}
export const isScheduledBefore: (
date: DateTime.DateTime
) => (appointment: Appointment) => boolean;
}
import * as Appointment from '@/schemas/Appointment';
declare const appointments: Array<Appointment.Appointment>;
const tomorrow = DateTime.addDuration(DateTime.unsafeNow(), Duration.days(1));
const beforeTomorrow = pipe(
appointments,
Array.filter(Appointment.isScheduledBefore(tomorrow))
);
Sorting Examples
import { Array, DateTime } from 'effect';
import * as Order from 'effect/Order';
import { pipe } from 'effect/Function';
declare module '@/schemas/Task' {
export interface Task {
readonly _tag: 'pending' | 'active' | 'completed';
readonly id: string;
readonly createdAt: DateTime.Utc;
}
export const OrderById: Order.Order<Task>;
export const OrderByPriority: Order.Order<Task>;
export const OrderByCreatedAt: Order.Order<Task>;
export const isPending: (task: Task) => boolean;
}
import * as Task from '@/schemas/Task';
declare const tasks: Array<Task.Task>;
const sortedById = Array.sort(tasks, Task.OrderById);
const sortedComplex = Array.sort(
tasks,
Order.combine(Task.OrderByPriority, Task.OrderByCreatedAt)
);
const sortedFiltered = pipe(
tasks,
Array.filter(Task.isPending),
Array.sort(Task.OrderByCreatedAt)
);
Pattern: Complex Filtering
Combine predicates for sophisticated queries:
import { DateTime, Duration, Array } from 'effect';
import { pipe } from 'effect/Function';
import * as Order from 'effect/Order';
import * as Equivalence from 'effect/Equivalence';
declare module '@/schemas/Appointment' {
export interface Appointment {
readonly id: string;
readonly date: DateTime.Utc;
readonly duration: Duration.Duration;
readonly status: string;
}
export const isScheduledThisWeek: (appointment: Appointment) => boolean;
export const hasMinimumDuration: (
min: Duration.Duration
) => (appointment: Appointment) => boolean;
export const isScheduledToday: (appointment: Appointment) => boolean;
export const OrderByStatusPriority: Order.Order<Appointment>;
export const OrderByScheduledTime: Order.Order<Appointment>;
export const EquivalenceById: Equivalence.Equivalence<Appointment>;
export const OrderByPriorityThenDate: Order.Order<Appointment>;
}
import * as Appointment from '@/schemas/Appointment';
declare const appointments: Array<Appointment.Appointment>;
const longThisWeek = pipe(
appointments,
Array.filter(Appointment.isScheduledThisWeek),
Array.filter(Appointment.hasMinimumDuration(Duration.hours(2)))
);
const sorted = pipe(
appointments,
Array.filter(Appointment.isScheduledToday),
Array.sort(
Order.combine(
Appointment.OrderByStatusPriority,
Appointment.OrderByScheduledTime
)
)
);
const uniqueSorted = pipe(
appointments,
Array.dedupeWith(Appointment.EquivalenceById),
Array.sort(Appointment.OrderByPriorityThenDate)
);
Checklist for Complete Coverage
Equality
Orders
Schedulable types
Durable types
Domain-specific fields
Documentation Requirements
Every predicate, equivalence, and order MUST have:
- JSDoc description
- @category tag
- @since tag
- @example with realistic usage showing imports and pipe
Key Patterns Summary
1. Deep Structural Equality (v4)
import { Schema, Equal } from 'effect';
const TaskSchema = Schema.TaggedStruct('task', { id: Schema.String });
type Task = Schema.Schema.Type<typeof TaskSchema>;
declare const t1: Task;
declare const t2: Task;
const areSame = Equal.equals(t1, t2);
2. Schema.toEquivalence() for Combinators
import { Schema, Array } from 'effect';
declare const Task: Schema.Schema<any, any, never>;
type Task = Schema.Schema.Type<typeof Task>;
export const Equivalence = Schema.toEquivalence(Task);
declare const tasks: Array<Task>;
const uniqueTasks = Array.dedupeWith(tasks, Equivalence);
3. Equivalence.mapInput for Field-Based
import * as Equivalence from 'effect/Equivalence';
interface Task {
readonly id: string;
}
const EquivalenceById = Equivalence.mapInput(
Equivalence.String,
(t: Task) => t.id
);
4. Equivalence.combine for Multi-Field
import * as Equivalence from 'effect/Equivalence';
interface Task {
readonly _tag: string;
readonly id: string;
}
declare const EquivalenceByTag: Equivalence.Equivalence<Task>;
declare const EquivalenceById: Equivalence.Equivalence<Task>;
const EquivalenceCombined = Equivalence.combine(
EquivalenceByTag,
EquivalenceById
);
5. Order.mapInput for Field-Based Sorting
import * as Order from 'effect/Order';
interface Task {
readonly id: string;
}
const OrderById = Order.mapInput(Order.String, (t: Task) => t.id);
6. Order.combine for Multi-Criteria Sorting
import * as Order from 'effect/Order';
interface Task {
readonly priority: number;
readonly date: Date;
}
declare const OrderByPriority: Order.Order<Task>;
declare const OrderByDate: Order.Order<Task>;
const OrderCombined = Order.combine(OrderByPriority, OrderByDate);
This ensures comprehensive equality checking, predicates, and sorting capabilities are discoverable and developers understand how to use them effectively with Effect's compositional patterns.