reopt designreopt design
DocsExploreToolsPricingBuilder
시작하기
시작하기
Playground
핵심 개념
핵심 개념
Calendar
Calendar 개요
이벤트 편집
반복 일정
타임존
예약 · 가용성
AI 스트리밍
원격 소스
앱 조합
구축·운영
스킬
릴리즈 노트
레퍼런스
Hook 레퍼런스
타입 레퍼런스
Oopt-calendar
reopt designreopt design

AI 시대를 위한 디자인 시스템

  • 문서
  • 가격
  • 릴리즈 노트
  • GitHub
  • 서비스 약관
  • 개인정보처리방침

© 2026 reopt-ai. All rights reserved.

레퍼런스
  1. 문서
  2. /
  3. 레퍼런스
  4. /
  5. 타입 레퍼런스

타입 레퍼런스

CalendarSpec, CalendarEvent, Booking, EventType, JsonPatchOp 등 핵심 타입 정의

reopt design · 업데이트 2026년 7월 2일

시작하기핵심 개념개요이벤트 편집반복 일정타임존예약 · 가용성AI 스트리밍원격 소스앱 조합스킬릴리즈 노트Hook 레퍼런스타입 레퍼런스

1. CalendarSpec & 5-맵

CalendarSpec은 다섯 개의 id-keyed 맵과 기본 timeZone, version으로 이루어진 평면 문서입니다. RFC 6902 JSON Patch로만 편집되며, additive-only 불변식을 지킵니다(필드 제거·용도 변경 금지, top-level 맵 이름 변경 금지).

스칼라 별칭 · enum

타입정의설명
TimeZoneIdstringIANA 타임존 id (예: Asia/Seoul)
ISODateTimestring오프셋 포함 ISO 8601 절대 시각(timed 이벤트)
ISODatestring타임존 없는 floating 날짜 YYYY-MM-DD(all-day)
RRuleStringstringRFC 5545 RRULE 본문(RRULE: 접두어 제외)
WallClockTimestring로컬 벽시계 HH:mm(가용성 규칙, 스케줄 tz 기준)
Weekday0 | 1 | … | 6요일(0=일요일, Date.getDay() 일치)
CalendarViewMode"month" | "week" | "day" | "agenda" | "timeline"뷰 모드
CalendarMode"interactive" | "stream" | "readonly"상호작용 모드(stream=AI 중 포인터 잠금)
CalendarVariant"events" | "booking"제품 지향 변형
TimeGridOptions{ startHour?, endHour?, hourHeight?, snapMinutes?, maxBodyHeight?, scrollToNow? }주/일 타임그리드 표시 옵션(Calendar.timeGrid)
EventStatus"confirmed" | "tentative" | "cancelled"이벤트 확정 상태
AttendeeStatus"needs-action" | "accepted" | …참석자 RSVP 상태
BookingStatus"pending" | "confirmed" | "cancelled" | "rejected"예약 라이프사이클 상태

CalendarSpec

tsx
interface CalendarSpec {
  /** spec 기본 IANA 타임존 (필수). */
  timeZone: TimeZoneId;
  /** 시간 축 — 모든 이벤트 (id → CalendarEvent). */
  events: Record<string, CalendarEvent>;
  /** 사이드바 — 캘린더/리소스 (id → Calendar). */
  calendars: Record<string, Calendar>;
  /** 명명된 가용성 스케줄 (id → AvailabilitySchedule). */
  availability: Record<string, AvailabilitySchedule>;
  /** 예약 카탈로그 — 예약 가능 타입 (id → EventType). */
  eventTypes: Record<string, EventType>;
  /** 예약 큐 — 예약 레코드 (id → Booking). */
  bookings: Record<string, Booking>;
  /** 낙관적 동시성 버전 (mutation마다 증가). */
  version: number;
}

/** 5개 top-level 맵의 키. */
type CalendarContainer =
  | "events"
  | "calendars"
  | "availability"
  | "eventTypes"
  | "bookings";

/** 컨테이너 + id로 엔티티를 가리키는 참조. */
interface EntityRef {
  container: CalendarContainer;
  id: string;
}

5개의 맵은 각각 독립 축을 모델링합니다: events=시간 축, calendars=사이드바, availability=명명된 스케줄, eventTypes=예약 카탈로그, bookings=예약 큐. 단일 kind 맵이 아니라 분리했기에 패치 경로가 자기 설명적입니다(/events/{id}/start).

Calendar (export명 CalendarResource)

calendars 맵의 엔티티입니다. 이름 충돌을 피하려 Calendar 컴포넌트와 구분해 CalendarResource로 export됩니다.

tsx
import type { CalendarResource } from "@reopt-ai/opt-calendar";

interface Calendar {
  id: string;
  name: string;
  color?: string;      // CSS 색상 또는 opt-ui 토큰
  hidden?: boolean;    // 렌더에서 제외하되 spec엔 유지
  primary?: boolean;   // 기본 캘린더 여부
  timeZone?: TimeZoneId;
  description?: string;
  meta?: Record<string, unknown>;
}

2. CalendarEvent

이벤트의 필수 핵심은 id·title·start·end뿐이며 나머지는 모두 선택입니다. timed 이벤트는 오프셋 포함 ISODateTime, all-day는 floating ISODate를 저장하고 end는 배타적입니다(RFC 5545).

tsx
interface CalendarEvent {
  // === 필수 핵심 ===
  id: string;
  title: string;
  start: ISODateTime | ISODate;  // inclusive
  end: ISODateTime | ISODate;    // exclusive

  // === 시간/소속 ===
  allDay?: boolean;              // true면 start/end는 ISODate
  timeZone?: TimeZoneId;         // 이벤트별 tz 오버라이드
  calendarId?: string;           // 소속 캘린더 (→ calendars)
  location?: string;
  description?: string;
  status?: EventStatus;          // 기본 "confirmed"
  attendees?: EventAttendee[];

  // === 반복 (series master) ===
  rrule?: RRuleString;           // 시리즈 마스터의 반복 규칙
  rdates?: ISODateTime[];        // 추가 1회성 occurrence (RDATE)
  exdates?: ISODateTime[];       // 제외 occurrence (EXDATE)

  // === 분리 override (detached) ===
  recurringEventId?: string;     // 마스터 이벤트 id (→ events)
  originalStart?: ISODateTime | ISODate; // 대체되는 occurrence 시작

  // === 예약 연결 ===
  bookingId?: string;            // 이 이벤트를 materialize한 예약 (→ bookings)

  // === 확장 ===
  meta?: Record<string, unknown>; // 네임스페이스 통합 데이터 (meta.kind 등)
}

반복 시리즈의 한 occurrence만 바꾸려면 그 시작을 exdates에 넣고, recurringEventId+originalStart를 가진 분리 override 이벤트를 추가합니다.

EventAttendee

tsx
type EventStatus = "confirmed" | "tentative" | "cancelled";
type AttendeeStatus =
  | "needs-action"
  | "accepted"
  | "declined"
  | "tentative";

interface EventAttendee {
  email: string;              // 신원
  name?: string;
  status?: AttendeeStatus;    // 기본 "needs-action"
  optional?: boolean;         // 참석 선택 여부
  organizer?: boolean;        // 주최자 여부
}

3. 예약 타입

예약은 "레코드 + materialized 이벤트" 모델입니다. EventType+AvailabilitySchedule은 공급 정의로 요청 시 BookingSlot을 생성하고(그리드에 그리지 않음), Booking은 확정 시 CalendarEvent로 materialize됩니다.

EventType

tsx
interface EventType {
  id: string;
  slug: string;               // URL 슬러그 (예: "30min")
  title: string;
  durationMinutes: number;
  description?: string;
  availabilityId?: string;    // 가용성 스케줄 (→ availability)
  calendarId?: string;        // 확정 예약이 materialize될 캘린더
  timeZone?: TimeZoneId;
  bufferBeforeMinutes?: number;
  bufferAfterMinutes?: number;
  minimumNoticeMinutes?: number; // 최소 예약 여유
  bookingWindowDays?: number;    // 며칠 앞까지 예약 허용
  seatsPerSlot?: number;         // >1이면 그룹 이벤트. 기본 1
  slotIntervalMinutes?: number;  // 슬롯 시작 간격. 생략 시 duration
  maxBookingsPerDay?: number;    // 하루 최대 접수 건수
  locationKind?: string;         // 예: "google-meet", "in-person"
  color?: string;
  hidden?: boolean;
  meta?: Record<string, unknown>;
}

AvailabilitySchedule

tsx
/** 주간 반복 가용성 창(벽시계, 스케줄 tz 기준). */
interface WeeklyAvailabilityRule {
  day: Weekday;
  start: WallClockTime;   // inclusive
  end: WallClockTime;     // exclusive
}

/** 특정 날짜 override(휴일/특별 영업시간). */
interface AvailabilityDateOverride {
  date: ISODate;
  windows: Array<{ start: WallClockTime; end: WallClockTime }>; // [] = 종일 휴무
}

/** 명명된 예약 가능 스케줄 (Cal.com "availability"). */
interface AvailabilitySchedule {
  id: string;
  name: string;
  timeZone?: TimeZoneId;  // 벽시계 규칙 해석 tz. 생략 시 spec tz
  rules: WeeklyAvailabilityRule[];
  overrides?: AvailabilityDateOverride[];
  meta?: Record<string, unknown>;
}

BookingSlot (파생)

이벤트 타입 + 가용성에서 이벤트/예약을 뺀 파생 슬롯입니다. spec에 저장되지 않습니다.

tsx
interface BookingSlot {
  eventTypeId: string;
  start: ISODateTime;
  end: ISODateTime;
  seatsRemaining?: number; // 그룹 타입의 잔여 좌석
}

Booking

tsx
type BookingStatus = "pending" | "confirmed" | "cancelled" | "rejected";

interface BookingAttendee {
  name: string;
  email: string;
  timeZone?: TimeZoneId;
  notes?: string;
}

interface Booking {
  id: string;
  eventTypeId: string;        // 예약 가능 타입 (→ eventTypes)
  start: ISODateTime;         // 확정 시작 (절대)
  end: ISODateTime;
  status: BookingStatus;
  attendee: BookingAttendee;  // 주 예약자
  guests?: BookingAttendee[];
  timeZone?: TimeZoneId;
  eventId?: string;           // materialize된 이벤트 (↔ CalendarEvent.bookingId)
  createdAt?: ISODateTime;
  meta?: Record<string, unknown>;
}

라이프사이클(create/confirm/cancel/reschedule)과 confirm→materialize 흐름은 예약 · 가용성 문서를 참고하세요.

4. 스토어 · 패치

드래그·리사이즈·수동 편집·AI 스트림이 모두 store.applyPatch(ops) 한 경로로 흐르며 하나의 undo 히스토리를 공유합니다. 편집 단위는 RFC 6902 JSON Patch op입니다.

JsonPatchOp

tsx
interface JsonPatchOp {
  /** 연산 종류. */
  op: "add" | "remove" | "replace" | "move" | "copy" | "test";
  /** spec 안으로의 RFC 6901 JSON Pointer (예: /events/e1/start). */
  path: string;
  /** add/replace/test의 값. */
  value?: unknown;
  /** move/copy의 소스 포인터. */
  from?: string;
}

CalendarStore

tsx
interface CalendarStore {
  /** 현재 spec (다음 mutation 전까지 안정 참조). */
  getSpec: () => CalendarSpec;
  /** RFC 6902 패치 적용. */
  applyPatch: (ops: JsonPatchOp[], options?: ApplyCalendarPatchOptions) => void;
  /** 변경 구독. 해제 함수를 반환 (useSyncExternalStore 호환). */
  subscribe: (listener: () => void) => () => void;
  undo: () => void;
  redo: () => void;
  canUndo: () => boolean;
  canRedo: () => boolean;
  /** 마지막 mutation이 바꾼 "container/id" 키. */
  getLastChangedIds: () => ReadonlySet<string>;
  /** 외부 스트림 시작 — 개별 applyPatch가 히스토리를 건너뜀. */
  beginExternalStream: () => void;
  /** 외부 스트림 종료 — 전체를 하나의 undo 엔트리로 기록. */
  endExternalStream: () => void;
  isExternalStreaming: () => boolean;
  /** forceRender로 증가하는 엔티티별 버전 카운터 (리마운트 키용). */
  getEntityVersions: () => ReadonlyMap<string, number>;
}

ApplyCalendarPatchOptions

tsx
interface ApplyCalendarPatchOptions {
  /** 변경 엔티티의 버전 카운터를 올려 뷰 리마운트를 강제(예: 드래그 중 AI 패치). */
  forceRender?: boolean;
  /** 텔레메트리/디버깅용 origin 태그(예: "drag", "ai", "remote"). */
  source?: string;
}

CalendarValidationResult

validateCalendarSpec(spec)가 구조 무결성(id/키 일치, end<start, 매달린 상호참조)을 검사해 반환합니다.

tsx
interface CalendarValidationError {
  container: string; // 문제 엔티티가 사는 컨테이너
  id: string;        // 엔티티 id
  message: string;   // 사람이 읽는 메시지
}

interface CalendarValidationResult {
  valid: boolean;    // 오류가 없으면 true
  errors: CalendarValidationError[];
}

5. 엔진 · 카탈로그 타입

반복 전개와 타임존 해석은 주입 가능한 seam입니다. 기본 minimalRecurrenceEngine·intlTimeZoneAdapter는 zero-dep이며, 같은 인터페이스로 npm rrule이나 Temporal 폴리필을 주입할 수 있습니다.

RecurrenceEngine

tsx
/** 반달 열림 전개 윈도우 [start, end) (일 정렬). */
interface RecurrenceRange {
  start: Date;
  end: Date;
}

/** 주입 가능한 반복 전개기. */
interface RecurrenceEngine {
  /** 범위 안, 마스터의 occurrence 시작 문자열들. */
  expand: (master: CalendarEvent, range: RecurrenceRange) => string[];
}

TimeZoneAdapter

tsx
/** 타임존에서 어떤 순간의 벽시계 파트. */
interface ZonedParts {
  year: number;
  month: number;
  day: number;
  hour: number;
  minute: number;
  weekday: Weekday;
}

/** 주입 가능한 타임존 어댑터. */
interface TimeZoneAdapter {
  /** tz에서 ISO 값의 지역화 표시 문자열. */
  format: (
    iso: ISODateTime | ISODate,
    tz?: TimeZoneId,
    opts?: Intl.DateTimeFormatOptions,
    locale?: string,
  ) => string;
  /** 절대 순간의 tz별 벽시계 파트. */
  getParts: (instant: Date, tz: TimeZoneId) => ZonedParts;
  /** tz에서 특정 날짜의 벽시계 시간 → 절대 순간. */
  wallClockToInstant: (
    date: ISODate,
    time: WallClockTime,
    tz: TimeZoneId,
  ) => Date;
}

CalendarEventInstance

expandEvents()가 반환하는, 인스턴스 시각에 놓인 구체 occurrence입니다. CalendarEvent를 확장합니다.

tsx
interface CalendarEventInstance extends CalendarEvent {
  /** 이 occurrence의 고유 키 (id@canonicalStart). */
  instanceKey: string;
  /** 엔진의 정규 occurrence 시작 (EXDATE/override 매칭용). */
  occurrenceStart?: string;
  /** 반복 규칙으로 생성됨(독립 이벤트와 구분). */
  isRecurring: boolean;
  /** 분리(override)된 occurrence 여부. */
  isOverride: boolean;
  /** 시리즈 마스터 id (반복 인스턴스/override). */
  masterId?: string;
}

CalendarCatalog

엔티티 종류 레지스트리로, AI 시스템 프롬프트·structured-output JSON Schema·attr 검증을 구동합니다. 각 kind는 인스턴스가 착지할 top-level 맵(container)을 선언합니다.

tsx
/** 스키마 필드 타입 힌트(iso-*, rrule은 프롬프트에서 의미 전달). */
type SchemaFieldType =
  | "string"
  | "number"
  | "boolean"
  | "string[]"
  | "iso-datetime"
  | "iso-date"
  | "rrule"
  | "object"
  | "object[]";

/** 필드 정의 — 순수 타입, 또는 default를 가진 객체. */
type SchemaFieldDef =
  | SchemaFieldType
  | { type: SchemaFieldType; default?: unknown };

/** 카탈로그의 엔티티 kind 하나. */
interface EntityDefinition {
  kind: string;                // 예: "event", "all-day-event", "booking"
  container: CalendarContainer;
  attrsSchema: Record<string, SchemaFieldDef>;
  zodSchema?: ZodLikeSchema;   // 런타임 검증용(선택)
  prompt?: string;
  examples?: string[];         // 프롬프트에 주입할 NDJSON 예제 라인
}

/** 함수 없는 직렬화 엔티티 스키마. */
interface EntitySchema {
  kind: string;
  container: CalendarContainer;
  attrsSchema: Record<string, SchemaFieldDef>;
  prompt?: string;
}

/** 직렬화된 카탈로그(JSON/서버 안전). */
interface CatalogSchema {
  entities: Record<string, EntitySchema>;
}

/** 카탈로그 레지스트리 + 생성기. */
interface CalendarCatalog {
  entities: Record<string, EntityDefinition>;
  prompt: (options?: { protocol?: "json-patch" }) => string;
  jsonSchema: () => Record<string, unknown>;
  toJSON: () => CatalogSchema;
  validateAttrs: (spec: CalendarSpec) => AttrsValidationResult;
}
PreviousHook 레퍼런스useCalendar, useCalendarRender, useCalendarNavigation, useCalendarRemoteSource, useCalendarSuggestion 시그니처와 예제레퍼런스
Hook 레퍼런스 페이지로 이동