@bimetal/calendar-rules
Calendar-domain rule pack: concrete rules, the domainRule factory, and withRules store integration. Built on @bimetal/rule-engine + @bimetal/calendar-data.
Installation
npm install @bimetal/calendar-rules
Built-in Rules
noOverlap — a CalendarRule value; rejects overlapping events
warnOnOverlap — a CalendarRule value; same check, but warning instead of error
workingHoursOnly — rejects events outside config.workingHours
minDuration(minutes), maxDuration(minutes) — duration guards
minGranularityDuration — derives min duration from config.granularity
domainRule({ id, description, domainKey, appliesTo?, evaluate }) — factory for rules that target events carrying a specific domain model
Store Integration
withRules(store, engine, config, caller?) — wraps a CalendarStore so every dispatch evaluates rules first; throws RuleViolationError on error-severity results
RuleAwareStore — CalendarStore plus lastWarnings for the most recent dispatch
RuleViolationError — thrown when a rule's evaluate returns { passed: false, severity: 'error' }
Calendar-flavored Types
CalendarRule — extends Rule<CalendarCommand, RuleContext> with optional domainKey
CalendarRuleResult — alias of the generic RuleResult
RuleContext — pinned to CalendarReadModel, CoreConfig, CalendarDateTime
RuleEngine — RuleEngine<CalendarCommand, RuleContext>
Convenience
createRuleEngine is re-exported from @bimetal/rule-engine so calendar consumers don't need a second import.
Example
import { createCalendarStore, createEvent } from '@bimetal/calendar-data';
import {
createRuleEngine,
noOverlap,
workingHoursOnly,
withRules,
} from '@bimetal/calendar-rules';
import { defaultConfig } from '@bimetal/calendar-core';
import { createDateTime, createTimeRange, Month } from '@bimetal/temporal';
const store = createCalendarStore();
const engine = createRuleEngine([noOverlap, workingHoursOnly]);
const guarded = withRules(store, engine, defaultConfig);
await guarded.dispatch(createEvent({
id: 'e1',
title: 'Standup',
allDay: false,
metadata: {},
timeRange: createTimeRange(
createDateTime(2026, Month.June, 17, 9, 0, 'UTC'),
createDateTime(2026, Month.June, 17, 10, 0, 'UTC'),
),
}));
Note on Domain Filtering
CalendarRule.domainKey is an opaque hint. The generic engine does not filter rules by it (that would couple @bimetal/rule-engine to the calendar event shape). Instead, domainRule() checks the target event's domains[domainKey] inside its own evaluate and returns PASS when the domain is absent — same behavior as before, but cleanly decoupled.
Replaces
This package contains the calendar-specific portion of the (removed) @bimetal/rules. The generic engine lives in @bimetal/rule-engine.
License
Apache License 2.0