import type { NotionAsCodeIcon, NotionAsCodeProperty, ResourceId } from "./intents.js"; // Ported from notion-next/src/server/helpers/infraAsCode/types.ts. export type ViewType = | "table" | "board" | "calendar" | "list" | "gallery" | "feed" | "timeline" | "custom"; export type PropertyVisibility = "show" | "hide" | "hide_if_empty"; /** * Format for a single visible property entry in a view (table / board / list / * gallery / feed / timeline). * * `property` must be the `resourceId` of a property in the view's data * source, matching the same convention used by board `groupBy.property`, * `calendarBy`, and `timelineBy`. * */ export type PropertyFormat = { /** ResourceId of the property in the view's data source. */ property: ResourceId; visible?: boolean; width?: number; visibility?: PropertyVisibility; }; /** * Format for a single column entry in a board view. * * `property` must be the `resourceId` of a property in the view's data * source, matching the same convention used by `PropertyFormat.property` * and board `groupBy.property`. * */ export type GroupFormat = { /** ResourceId of the property in the view's data source. */ property: ResourceId; hidden?: boolean; value?: { type: string; value?: string | boolean | number | null; }; }; export type GroupByFormatBase = { /** ResourceId of the property in the view's data source. */ property: ResourceId; /** Whether groups with no pages are visible. Defaults to "show". */ emptyGroupVisibility?: "show" | "hide"; statusBy?: never; }; export type SelectGroupByFormat = GroupByFormatBase & { type: "select" | "multi_select"; }; export type StatusGroupByFormat = Omit & { type: "status"; /** Groups status values by their canonical status group or individual option. */ statusBy?: "group" | "option"; }; export type PersonGroupByFormat = GroupByFormatBase & { type: "person" | "created_by" | "last_edited_by"; }; export type DateGroupByFormat = GroupByFormatBase & { type: "date" | "created_time" | "last_edited_time" | "last_visited_time"; }; export type TextGroupByFormat = GroupByFormatBase & { type: "text" | "title" | "url" | "email" | "phone_number"; }; export type NumberGroupByFormat = GroupByFormatBase & { type: "number"; }; export type CheckboxGroupByFormat = GroupByFormatBase & { type: "checkbox"; }; export type RelationGroupByFormat = GroupByFormatBase & { type: "relation"; }; export type LocationGroupByFormat = GroupByFormatBase & { type: "location"; }; export type FormulaGroupByFormat = GroupByFormatBase & { type: "formula"; }; /** * Board view group-by configuration. * * `property` must be the `resourceId` of a property in the view's data * source, matching the same convention used by `PropertyFormat.property`, * `CalendarViewSchema.calendarBy`, and `TimelineViewSchema.timelineBy`. * */ export type GroupByFormat = | SelectGroupByFormat | StatusGroupByFormat | PersonGroupByFormat | DateGroupByFormat | TextGroupByFormat | NumberGroupByFormat | CheckboxGroupByFormat | RelationGroupByFormat | LocationGroupByFormat | FormulaGroupByFormat; export type CoverFormat = | { type: "page_cover" } | { type: "page_content" } | { type: "page_content_first" } | { type: "property"; property: string }; export type CoverSizeFormat = "small" | "medium" | "large"; export type CoverAspectFormat = "contain" | "cover"; export type DatabaseViewSortDirection = "ascending" | "descending"; /** * Sort schema for ordering database view results. * Sorts are applied in order — all pages are first sorted by the first sort, * then ties are broken by the second sort, and so on. */ export type PropertyViewSortSchema = { propertyId: string; direction: DatabaseViewSortDirection; }; type PropertyType = NotionAsCodeProperty["type"]; export type DatePropertyTypes = "date" | "created_time" | "last_edited_time"; export type BasePropertyFilter = { propertyId: string; type: "property"; }; export type TextPropertyFilter = { propertyType: Extract; operator: | "string_is" | "string_is_not" | "string_contains" | "string_does_not_contain" | "string_starts_with" | "string_ends_with"; value: string; } & BasePropertyFilter; export type NumberPropertyFilter = { propertyType: Extract; operator: | "number_equals" | "number_does_not_equal" | "number_greater_than" | "number_less_than" | "number_greater_than_or_equal_to" | "number_less_than_or_equal_to"; value: number; } & BasePropertyFilter; export type CheckboxPropertyFilter = { propertyType: Extract; operator: "checkbox_is" | "checkbox_is_not"; value: boolean; } & BasePropertyFilter; export type SelectPropertyFilter = { propertyType: Extract; operator: "enum_is" | "enum_is_not"; value: Array; } & BasePropertyFilter; export type MultiSelectPropertyFilter = { propertyType: Extract; operator: "enum_contains" | "enum_does_not_contain" | "enum_contains_all"; value: Array; } & BasePropertyFilter; export type StatusPropertyFilter = { propertyType: Extract; operator: "status_is" | "status_is_not"; value: Array; } & BasePropertyFilter; /** * Date presets resolved relative to today, matching options such as * "Today", "Tomorrow", and "One week ago". */ export type RelativeDatePreset = | "today" | "tomorrow" | "yesterday" | "one_week_ago" | "one_week_from_now" | "one_month_ago" | "one_month_from_now"; /** * The unit spanned by a relative "is relative to today" date range. */ export type RelativeDateRangeUnit = "day" | "week" | "month" | "year"; /** * Which date a date property filter compares against. Notion date properties * can hold a range, and `end_date` reads the far end of it. */ export type DateFilterMode = "start_date" | "end_date"; export type ExactDatePropertyFilterValue = { type: "exact"; value: string }; export type RelativeDatePropertyFilterValue = { type: "relative"; value: RelativeDatePreset; }; export type RelativeToTodayDatePropertyFilterValue = { type: "relative_to_today"; value: { direction: "past" | "next" | "this"; count?: number; unit: RelativeDateRangeUnit; }; }; /** * Value for the `date_is_within` operator, shown as "Is between" in the UI. * * Bounds are inclusive `YYYY-MM-DD` dates; supplying only one leaves the range * open at the other end. * */ export type ExactRangeDatePropertyFilterValue = { type: "exact_range"; value: { startDate: string; endDate?: string } | { startDate?: string; endDate: string }; }; /** * Value for a date property filter. * * - `exact` matches a calendar date in `YYYY-MM-DD` format. * - `relative` matches a date preset such as today or tomorrow. * - `relative_to_today` matches a range relative to today. `unit` can be * `day`, `week`, `month`, or `year`. `count` defaults to `1` when omitted * and is unnecessary for `direction: "this"`. * - `exact_range` matches a fixed calendar range, optionally open at one end. * */ export type DatePropertyFilterValue = | ExactDatePropertyFilterValue | RelativeDatePropertyFilterValue | RelativeToTodayDatePropertyFilterValue | ExactRangeDatePropertyFilterValue; /** * Date property filter. * * Single-date operators accept `exact` or `relative` values. * `date_is_relative_to` requires a `relative_to_today` value. * `date_is_within` requires an `exact_range` value. * * `dateFilterMode` chooses which end of a date range the comparison reads. * It defaults to `"start_date"`. `"end_date"` falls back to the start date for * rows whose date is a single day rather than a range, and has no effect on * `created_time` / `last_edited_time` properties, which are never ranges. * * @example Exact date * { * type: "property", * propertyId: "due", * propertyType: "date", * operator: "date_is_on_or_after", * value: { type: "exact", value: "2025-10-03" } * } * * @example Relative date preset * { * type: "property", * propertyId: "due", * propertyType: "date", * operator: "date_is", * value: { type: "relative", value: "today" } * } * * @example Next two months * { * type: "property", * propertyId: "due", * propertyType: "date", * operator: "date_is_relative_to", * value: { type: "relative_to_today", value: { direction: "next", count: 2, unit: "month" } } * } * * @example Sprints that end this week * { * type: "property", * propertyId: "sprint", * propertyType: "date", * operator: "date_is_relative_to", * value: { type: "relative_to_today", value: { direction: "this", unit: "week" } }, * dateFilterMode: "end_date" * } * * @example Fixed calendar range * { * type: "property", * propertyId: "due", * propertyType: "date", * operator: "date_is_within", * value: { type: "exact_range", value: { startDate: "2026-01-01", endDate: "2026-03-31" } } * } * * @example Range left open at the end * { * type: "property", * propertyId: "due", * propertyType: "date", * operator: "date_is_within", * value: { type: "exact_range", value: { startDate: "2026-01-01" } } * } * */ export type DatePropertyFilter = | ({ propertyType: DatePropertyTypes; operator: | "date_is" | "date_is_before" | "date_is_after" | "date_is_on_or_before" | "date_is_on_or_after"; value: ExactDatePropertyFilterValue | RelativeDatePropertyFilterValue; dateFilterMode?: DateFilterMode; } & BasePropertyFilter) | ({ propertyType: DatePropertyTypes; operator: "date_is_relative_to"; value: RelativeToTodayDatePropertyFilterValue; dateFilterMode?: DateFilterMode; } & BasePropertyFilter) | ({ propertyType: DatePropertyTypes; operator: "date_is_within"; value: ExactRangeDatePropertyFilterValue; dateFilterMode?: DateFilterMode; } & BasePropertyFilter); export type RelationPropertyFilter = { propertyType: Extract; operator: "relation_contains" | "relation_does_not_contain"; /** References related pages, or the current page when rendered as a page-layout tab. */ value: { type: "exact"; value: Array } | { type: "relative"; value: "this_page" }; } & BasePropertyFilter; /** * Person property filter — matches rows whose person property contains (or * does not contain) the viewer (the `"me"` template variable, resolved at * read time). */ export type PersonPropertyFilter = { propertyType: Extract; operator: "person_contains" | "person_does_not_contain"; // TODO: Support more Person property types in the future value: [{ type: "relative"; value: "me" }]; } & BasePropertyFilter; /** * Filter schema for filtering database views by property values. * Used in view definitions to specify which pages should be visible. * * Each filter targets a specific property by ID and applies a type-appropriate * filter operator with a comparison value. * * @example Text property filter * { * propertyId: "name-prop", * type: "property", * propertyType: "text", * operator: "string_contains", * value: "Project" * } */ export type PropertyFilterSchema = | TextPropertyFilter | NumberPropertyFilter | CheckboxPropertyFilter | SelectPropertyFilter | MultiSelectPropertyFilter | StatusPropertyFilter | DatePropertyFilter | RelationPropertyFilter | PersonPropertyFilter; export type FilterSchema = Array; export type AdvancedFilterSchema = { type: "advanced"; operator: "and" | "or"; filters: FilterSchema; }; /** * Base view schema shared by all view types. * * `dataSourceResourceId` must reference a data source created by the script or * supplied through existing resources. * * Database declarations require at least one data source or at least one view. * A linked-only database may omit `datasources`, or use `datasources: {}`, only * when its `views` list is nonempty. `{}`, `{ datasources: {} }`, and * `{ datasources: {}, views: [] }` are invalid. * * @example * // A linked-only database can use a typed table view for an explicit source. * const linkedTableView: TableViewSchema = { * resourceId: "issues-table-view", * type: "table", * dataSourceResourceId: "issues-source", * properties: [{ property: "issue-title", visible: true }] * }; * const linkedDatabase = notion.database("issues-linked-database", { * parent: { type: "resourceId", resourceId: "project-page" }, * views: [linkedTableView] * }) * * @example * // When creating a database with a data source * const db = notion.database("my-database", { * dataSourceResourceId: "my-datasource", * name: "Main", * schema: { Name: { resourceId: "my-title", type: "title" } } * }) * * // Views must reference that data source * db.addView({ * resourceId: "my-table-view", * type: "table", * dataSourceResourceId: "my-datasource" // REQUIRED: matches data source above * }) * * @example * // The explicit empty `datasources` form is valid with a nonempty view list. * const explicitLinkedDatabase = notion.database("issues-linked-database-explicit", { * datasources: {}, * views: [{ * resourceId: "issues-table-view-explicit", * type: "table", * dataSourceResourceId: "issues-source", * properties: [{ property: "issue-title", visible: true }] * }] * }) */ export type BaseViewSchema = { resourceId: ResourceId; name?: string; /** * Notion icon shown in the view tab, including tabs in database page layouts. * View icons do not support emoji or uploaded files. Omit to use the default * icon for the view type. */ icon?: Extract; type: ViewType; /** Resource ID of a script-created or existing data source. */ dataSourceResourceId: ResourceId; /** * Optional resource ID of a template page in this view's data source to use as * the view default template. * * The referenced page must be created with `template: true` and be parented to * this same data source. */ defaultTemplate?: ResourceId; /** * Optional: create this view as a linked view referenced by `` tags * in page content markdown or use it as a tab in a database layout. It does * not attach to the database block's main view tabs. */ ephemeral?: boolean; sorts?: Array; /** Optional filter to control which pages appear in this view. */ filters?: FilterSchema; /** Whether page icons are shown in this view. */ showPageIcon?: boolean; }; export type TableViewSchema = BaseViewSchema & { type: "table"; properties?: Array; wrap?: boolean; groupBy?: GroupByFormat; }; export type BoardViewSchema = BaseViewSchema & { type: "board"; properties?: Array; groupBy?: GroupByFormat; columns?: Array; cover?: CoverFormat; coverSize?: CoverSizeFormat; coverAspect?: CoverAspectFormat; wrap?: boolean; }; export type CalendarViewSchema = BaseViewSchema & { type: "calendar"; properties?: Array; /** * REQUIRED: ResourceId of the date property to use for the calendar. * Must match the `resourceId` of a date property in the database schema. * * @example * calendarBy: "due-date-prop" // resourceId of a property of type "date" */ calendarBy: ResourceId; showWeekends?: boolean; }; export type ListViewSchema = BaseViewSchema & { type: "list"; properties?: Array; groupBy?: GroupByFormat; }; export type GalleryViewSchema = BaseViewSchema & { type: "gallery"; properties?: Array; cover?: CoverFormat; coverSize?: CoverSizeFormat; coverAspect?: CoverAspectFormat; }; export type FeedViewSchema = BaseViewSchema & { type: "feed"; properties?: Array; wrap?: boolean; showAuthorByline?: boolean; }; export type TimelineViewSchema = BaseViewSchema & { type: "timeline"; properties?: Array; tableProperties?: Array; /** * REQUIRED: ResourceId of the date property to use for the timeline start. * Must match the `resourceId` of a date property in the database schema. * * @example * timelineBy: "start-date-prop" // resourceId of a property of type "date" */ timelineBy: ResourceId; /** * Optional: ResourceId of the date property to use for the timeline end * (for date ranges). Must match the `resourceId` of a date property in * the database schema. */ timelineByEnd?: ResourceId; showTable?: boolean; }; export type CustomViewSchema = Omit< BaseViewSchema, "type" | "sorts" | "filters" | "showPageIcon" | "defaultTemplate" > & { type: "custom"; customBlockKey: string; sorts?: never; filters?: never; properties?: never; showPageIcon?: never; defaultTemplate?: never; }; export type ViewSchema = | TableViewSchema | BoardViewSchema | CalendarViewSchema | ListViewSchema | GalleryViewSchema | FeedViewSchema | TimelineViewSchema | CustomViewSchema;