domain-predicates
from front-depiction/claude-setup
Reusable Claude Code configuration for Effect TypeScript projects with specialized agents and skills
10 stars4 forksUpdated Jan 19, 2026
npx skills add https://github.com/front-depiction/claude-setup --skill domain-predicatesSKILL.md
Domain Predicates Skill
Generate complete sets of predicates and Order instances for domain types, derived from typeclass implementations.
Pattern: Equality with Schema.Data
When using schemas, leverage Schema.Data for automatic structural equality:
import { Schema, Equal, DateTime } from "effect"
export const Task = Schema.TaggedStruct("pending", {
id: Schema.String,
createdAt: Schema.DateTimeUtcFromSelf,
}).pipe(Schema.Data) // Implements Equal.Symbol automatically
export type Task = Schema.Schema.Type<typeof Task>
declare const makeTask: (props: { id: string; createdAt: DateTime.Utc }) => Task
declare const now: DateTime.Utc
// Usage: Automatic structural equality
const task1 = makeTask({ id: "123", createdAt: now })
const task2 = makeTask({ id: "123", createdAt: now })
Equal.equals(task1, task2) // true - structural equality
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>
// Derive from schema (structural equality)
export const TaskEquivalence = Schema.equivalence(Task)
declare const tasks: Array<Task>
// Usage with combinators
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
}
/**
* Compare tasks by ID only.
*
* @category Equivalence
* @since 0.1.0
* @example
* import * as Task from "@/schemas/Task"
* import * as Array from "effect/Array"
*
* const uniqueById = Array.dedupeWith(tasks, Task.EquivalenceById)
*/
export const EquivalenceById = Equivalence.mapInput(
Equivalence.string,
(task: Task) => task.id
)
/**
* Compare by status tag.
*
* @category Equivalence
* @since 0.1.0
*/
export const EquivalenceByTag = Equivalence.mapInput(
Equivalence.string,
(task: Task) => task._tag
)
/**
* Compare by creation date.
*
* @category Equivalence
* @since 0.1.0
*/
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 multi-field equality:
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>
/**
* Compare by tag first, then by ID.
*
* Both must match for equivalence.
*
* @category Equivalence
* @since 0.1.0
* @example
* import * as Task from "@/schemas/Task"
*
* const areSame = Task.EquivalenceByTagAndId(task1, task2)
*/
export const EquivalenceByTagAndId = Equivalence.combine(
EquivalenceByTag,
EquivalenceById
)
/**
* Compare by multiple criteria for exact equality.
*
* @category Equivalence
* @since 0.1.0
*/
export const EquivalenceComplete = Equivalence.combine(
EquivalenceByTag,
EquivalenceById,
EquivalenceByCreatedAt
)
Key Pattern: Equivalence.combine
- Combines multiple 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"
// Inline typeclass interface declarations (instead of module augmentations)
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
}
// Typeclass module declarations
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: SchedulableI
...
Repository Stats
Stars10
Forks4