/** * Platform constants for the API. Values mirror the Prisma schema enums so * the runtime can validate incoming values before they hit the database. */ export const WORKSPACE_ROLES = ["OWNER", "ADMIN", "MEMBER", "GUEST"] as const; export type WorkspaceRole = (typeof WORKSPACE_ROLES)[number]; // T-136: space types (Confluence expansion). Legacy workspaces read as "team". export const WORKSPACE_TYPES = ["team", "project", "kb", "custom"] as const; export type WorkspaceType = (typeof WORKSPACE_TYPES)[number]; export const PERMISSION_LEVELS = ["NONE", "VIEW", "COMMENT", "EDIT", "FULL_ACCESS"] as const; export type PermissionLevel = (typeof PERMISSION_LEVELS)[number]; /** 32 block types (see prisma schema `BlockType`). */ export const BLOCK_TYPES = [ "PARAGRAPH", "HEADING_1", "HEADING_2", "HEADING_3", "QUOTE", "CALLOUT", "BULLETED_LIST", "NUMBERED_LIST", "TODO", "TOGGLE", "IMAGE", "VIDEO", "AUDIO", "FILE", "BOOKMARK", "EMBED", "CODE", "EQUATION", "COLUMN", "COLUMN_LIST", "DIVIDER", "SPACER", "DATABASE", "LINKED_DATABASE", "TABLE", "CHART", "FORM", "SYNCED_BLOCK", "CHILD_PAGE", "CHILD_DATABASE", "TOC" ] as const; export type BlockType = (typeof BLOCK_TYPES)[number]; /** 24 property types (see prisma schema `PropertyType`). */ export const PROPERTY_TYPES = [ "TITLE", "TEXT", "RICH_TEXT", "NUMBER", "SELECT", "MULTI_SELECT", "STATUS", "DATE", "DATE_RANGE", "PERSON", "EMAIL", "PHONE", "URL", "CHECKBOX", "FILES", "CREATED_TIME", "CREATED_BY", "LAST_EDITED_TIME", "LAST_EDITED_BY", "RELATION", "ROLLUP", "FORMULA", "BUTTON", "UNIQUE_ID" ] as const; export type PropertyType = (typeof PROPERTY_TYPES)[number]; export const VIEW_TYPES = [ "TABLE", "LIST", "BOARD", "GALLERY", "CALENDAR", "TIMELINE", "CHART", "DASHBOARD", "FORM" ] as const; export type ViewType = (typeof VIEW_TYPES)[number]; export const RELATION_CARDINALITIES = ["ONE_TO_ONE", "ONE_TO_MANY", "MANY_TO_MANY"] as const; export type RelationCardinality = (typeof RELATION_CARDINALITIES)[number]; export const ROLLUP_AGGREGATES = ["COUNT", "SUM", "AVERAGE", "MIN", "MAX", "PERCENT", "UNIQUE"] as const; export type RollupAggregate = (typeof ROLLUP_AGGREGATES)[number]; export const AUTOMATION_TRIGGER_TYPES = [ "RECORD_CREATED", "RECORD_UPDATED", "PROPERTY_CHANGED", "PAGE_CREATED", "FORM_SUBMITTED", "SCHEDULE", "WEBHOOK", "MANUAL" ] as const; export type AutomationTriggerType = (typeof AUTOMATION_TRIGGER_TYPES)[number]; export const AUTOMATION_ACTION_TYPES = [ "UPDATE_RECORD", "CREATE_PAGE", "SEND_EMAIL", "SEND_WEBHOOK", "CREATE_NOTIFICATION", "ASSIGN_USER", "CHANGE_STATUS", "CALL_INTEGRATION", "RUN_AI_ACTION" ] as const; export type AutomationActionType = (typeof AUTOMATION_ACTION_TYPES)[number]; export const AUTOMATION_STATUSES = ["DRAFT", "ACTIVE", "PAUSED", "ARCHIVED"] as const; export type AutomationStatus = (typeof AUTOMATION_STATUSES)[number]; export const FORM_QUESTION_TYPES = [ "SHORT_TEXT", "LONG_TEXT", "NUMBER", "EMAIL", "DATE", "CHECKBOX", "SINGLE_SELECT", "MULTI_SELECT", "RATING", "FILE" ] as const; export type FormQuestionType = (typeof FORM_QUESTION_TYPES)[number]; export const FORM_STATUSES = ["DRAFT", "PUBLISHED", "CLOSED"] as const; export type FormStatus = (typeof FORM_STATUSES)[number]; export const API_KEY_SCOPES = ["READ", "WRITE", "ADMIN"] as const; export type ApiKeyScope = (typeof API_KEY_SCOPES)[number]; export const SHARE_SCOPES = ["PRIVATE", "WORKSPACE", "TEAM", "USERS", "PUBLIC"] as const; export type ShareScope = (typeof SHARE_SCOPES)[number]; export const FILE_STATUSES = ["UPLOADING", "SCANNING", "READY", "VIRUS_DETECTED", "FAILED"] as const; export type FileStatus = (typeof FILE_STATUSES)[number]; /** * Filter operators supported by the record query engine. The JS pass * (`leafMatches` in query.service.ts) is the exact evaluator and * understands all of these; `lib/filter-builder.ts` uses the same set to * validate (op, type) triples and emits a SQL pre-filter only for the small * subset Prisma can express (CHECKBOX eq/neq on the raw-scalar column). * * The docs §5.3 names (eq/neq/contains/…) and the web filter-builder's * names (is/equals/is_empty/starts_with/…) are reconciled via OP_ALIASES * (query.service.ts) before reaching here. */ export const FILTER_OPS = [ "eq", "neq", "contains", "ncontains", "startsWith", "endsWith", "isEmpty", "isNotEmpty", "gt", "gte", "lt", "lte", "before", "after", "between", "isThisWeek", "isThisMonth", "withinNextDays", "isPast", "isFuture", "contains_any", "contains_all", "isCurrentUser" ] as const; export type FilterOp = (typeof FILTER_OPS)[number]; export const APP_VERSION = "0.1.0"; export const API_PREFIX = "api/v1"; export const REFRESH_COOKIE = "factory_refresh"; export const EVENTS_STREAM = "factory:events"; export const EVENTS_STREAM_MAXLEN = 10000; export const MAX_FILE_SIZE_DEFAULT = 50 * 1024 * 1024; // 50 MB export const MAX_BLOCK_BATCH = 200; export const MAX_BULK_RECORDS = 500; export const MAX_EXPORT_ROWS = 50_000; export const MAX_BLOCK_TREE = 5000; // T-165 CSV import caps (tracked): the JSON body is buffered by a // route-scoped parser at MAX_CSV_IMPORT_BODY_BYTES (the global 256 KB limit // would 413 a valid 10k-row file), and the parsed table is capped at // MAX_CSV_IMPORT_ROWS. export const MAX_CSV_IMPORT_ROWS = 10_000; export const MAX_CSV_IMPORT_BODY_BYTES = 2 * 1024 * 1024; // 2 MB // T-167 whiteboard canvas cap: a single canvas save is one JSON document. The // global 256 KB JSON ceiling would 413 a legitimate large canvas, so the save // route buffers up to this cap via a route-scoped parser (see // common/whiteboard-body.ts). Sized for the 2,000-object render/perf gate. export const MAX_WHITEBOARD_BODY_BYTES = 2 * 1024 * 1024; // 2 MB /** Default database seed for new databases. */ export const DEFAULT_DB_PROPERTIES = [ { name: "Title", type: "TITLE", isTitle: true }, { name: "Status", type: "SELECT", config: { options: [ { name: "To Do", color: "gray" }, { name: "In Progress", color: "blue" }, { name: "Done", color: "green" } ] } } ] as const; /** Domain events published on the Redis stream `factory:events`. */ export const EVENTS = { PAGE_CREATED: "page.created", PAGE_UPDATED: "page.updated", PAGE_DELETED: "page.deleted", PAGE_SHARED: "page.shared", DATABASE_CREATED: "database.created", DATABASE_UPDATED: "database.updated", RECORD_CREATED: "record.created", RECORD_UPDATED: "record.updated", COMMENT_CREATED: "comment.created", // TASK-0094 — the comment fan-out events (stream-internal, NOT in the // public webhook catalog — the realtime app maps each to a `comment.event` // frame kind; the mapping lives in @factory/shared/realtime/comment-events. // `comment.created` doubles as the reply event (its payload carries // `replyId`). All five are additive — the catalog row `comment.created` // (Phase 1) and `comment.resolved` stay for Phase-13 webhooks. COMMENT_RESOLVED: "comment.resolved", COMMENT_REOPENED: "comment.reopened", COMMENT_REACTION: "comment.reaction", COMMENT_DELETED: "comment.deleted", // TASK-0094 R7 — a Notification row was created; the worker's email // decision (R6) + the realtime `notification.push` fan-out both consume it. NOTIFICATION_CREATED: "notification.created", FORM_SUBMITTED: "form.submitted", AUTOMATION_EXECUTED: "automation.executed", AUTOMATION_MANUAL: "automation.manual", FILE_UPLOADED: "file.uploaded", API_KEY_CREATED: "api_key.created", USER_LOGIN: "user.login", USER_LOGOUT: "user.logout", USER_REGISTER: "user.register", PERMISSION_CHANGED: "permission.changed", WORKSPACE_CREATED: "workspace.created" } as const; export type EventName = (typeof EVENTS)[keyof typeof EVENTS];