/** * @factory/shared — workspace DTOs (PRODUCT-SPEC §2, tenancy model). */ import { z } from "zod"; import { LIMITS } from "../constants.js"; export const workspaceRoleSchema = z.enum(["owner", "admin", "member", "guest"]); export type WorkspaceRoleInput = z.infer; export const workspaceSlugSchema = z .string() .min(2, "Slug must be at least 2 characters") .max(LIMITS.MAX_SLUG, "Slug must be at most 50 characters") .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Slug must be lowercase letters, digits and dashes"); export type WorkspaceSlugInput = z.infer; export const createWorkspaceSchema = z .object({ name: z .string() .min(2, "Name must be at least 2 characters") .max(LIMITS.MAX_WORKSPACE_NAME, "Name must be at most 50 characters"), slug: workspaceSlugSchema, description: z.string().max(500).optional(), }) .strict(); export type CreateWorkspace = z.infer; export const updateWorkspaceSchema = z .object({ name: z.string().min(2).max(LIMITS.MAX_WORKSPACE_NAME).optional(), description: z.string().max(500).optional().nullable(), icon: z.string().max(32).optional().nullable(), }) .strict() .refine((v) => Object.keys(v).length > 0, { message: "No fields to update" }); export type UpdateWorkspace = z.infer; /** Add an existing user to a workspace with a role. */ export const addMemberSchema = z .object({ email: z.string().min(1).email(), role: z.enum(["owner", "admin", "member", "guest"]), }) .strict(); export type AddMember = z.infer; /** Invite by email — creates an Invitation row (accepts later). */ export const inviteMemberSchema = z .object({ email: z.string().min(1).email(), role: z.enum(["admin", "member", "guest"]).default("member"), /** Team to place the member in once accepted (teamspaces). */ teamId: z.string().min(1).optional(), }) .strict(); export type InviteMember = z.infer;