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-predicates

SKILL.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

...
Read full content

Repository Stats

Stars10
Forks4