{"version":3,"sources":["../src/validators/fields.ts","../src/validators/base-filter.ts","../src/validators/base-query.ts","../src/validators/parse-request-params.ts","../src/validators/to-string-array.ts","../src/validators/zod-schema-utils.ts","../src/validators/extract-search-params.ts","../src/validators/build-filter-search-params.ts"],"names":["z"],"mappings":"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","file":"validators.mjs","sourcesContent":["/**\n * 공통 Zod 필드 스키마\n *\n * 폼 스키마를 조합할 때 필드 단위로 픽해서 사용한다.\n *\n * @example\n * import { z } from \"zod\";\n * import { phoneField, bizNumField, passwordField } from \"@linkup/validators\";\n *\n * const memberSchema = z.object({\n *   name:     z.string().min(1, \"이름을 입력해주세요.\"),\n *   phone:    phoneField,\n *   bizNo:    bizNumField.optional(),\n *   password: passwordField,\n * });\n */\n\nimport { z } from \"zod\";\n\n// ─── 문자열 기본 ──────────────────────────────────────────────────────────────\n\n/** 필수 문자열 */\nexport const requiredStringField = z.string().min(1, \"필수 항목입니다.\");\n\n/** 선택 문자열 (빈 문자열 허용) */\nexport const optionalStringField = z.string().optional().or(z.literal(\"\"));\n\n// ─── 연락처 ───────────────────────────────────────────────────────────────────\n\n/**\n * 전화번호 — 숫자 10~11자리 (하이픈 제외)\n * AdminFormField type=\"tel\" 과 함께 사용하면 자동 포맷팅됨\n */\nexport const phoneField = z\n    .string()\n    .regex(/^\\d{10,11}$/, \"올바른 전화번호를 입력해주세요. (숫자 10~11자리)\");\n\n/**\n * 사업자번호 — 숫자 10자리 (하이픈 제외)\n * AdminFormField type=\"bizNum\" 과 함께 사용하면 자동 포맷팅됨\n */\nexport const bizNumField = z\n    .string()\n    .regex(/^\\d{10}$/, \"올바른 사업자번호를 입력해주세요. (숫자 10자리)\");\n\n// ─── 이메일 ───────────────────────────────────────────────────────────────────\n\nexport const emailField = z\n    .string()\n    .min(1, \"이메일을 입력해주세요.\")\n    .email(\"올바른 이메일 형식이 아닙니다.\");\n\n// ─── 비밀번호 ─────────────────────────────────────────────────────────────────\n\n/**\n * 비밀번호 — 8자 이상, 영문 + 숫자 포함\n */\nexport const passwordField = z\n    .string()\n    .min(8, \"비밀번호는 8자 이상이어야 합니다.\")\n    .refine((v) => /[a-zA-Z]/.test(v), \"영문을 포함해야 합니다.\")\n    .refine((v) => /\\d/.test(v), \"숫자를 포함해야 합니다.\");\n\n// ─── 날짜 ────────────────────────────────────────────────────────────────────\n\n/**\n * 날짜 — YYYY-MM-DD 형식\n * AdminDatePicker / AdminDateInput 의 value 타입과 일치\n */\nexport const dateField = z\n    .string()\n    .regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"날짜 형식이 올바르지 않습니다. (YYYY-MM-DD)\");\n\n/** 선택 날짜 (빈 문자열 허용) */\nexport const optionalDateField = z\n    .string()\n    .refine(\n        (v) => v === \"\" || /^\\d{4}-\\d{2}-\\d{2}$/.test(v),\n        \"날짜 형식이 올바르지 않습니다. (YYYY-MM-DD)\"\n    );\n\n// ─── 숫자 ────────────────────────────────────────────────────────────────────\n\n/**\n * 콤마 포맷 숫자 문자열 (예: \"1,234\")\n * AdminFormField type=\"comma\" 와 함께 사용\n */\nexport const commaNumberField = z\n    .string()\n    .refine(\n        (v) => v === \"\" || /^[\\d,]+$/.test(v),\n        \"숫자만 입력 가능합니다.\"\n    );\n\n/** 양의 정수 */\nexport const positiveIntField = z\n    .number({ error: \"숫자를 입력해주세요.\" })\n    .int(\"정수만 입력 가능합니다.\")\n    .positive(\"0보다 큰 숫자를 입력해주세요.\");\n\n// ─── URL ─────────────────────────────────────────────────────────────────────\n\nexport const urlField = z\n    .string()\n    .url(\"올바른 URL 형식이 아닙니다.\")\n    .optional()\n    .or(z.literal(\"\"));\n","/**\n * baseFilterSchema — 관리자 목록 API 공통 필터 스키마\n *\n * 모든 목록 페이지에 공통으로 들어가는 필드를 정의한다.\n * .extend()로 프로젝트별 필드를 추가해서 사용한다.\n *\n * @example\n * // 기본 그대로 사용\n * type MemberFilter = BaseFilter;\n * const init = baseFilterDefaults;\n *\n * @example\n * // 프로젝트별 필드 추가\n * const memberFilterSchema = baseFilterSchema.extend({\n *   role:   z.string().default(\"\"),\n *   grade:  z.string().default(\"\"),\n * });\n * type MemberFilter = z.infer<typeof memberFilterSchema>;\n * const initFilters = memberFilterSchema.parse({});  // 모든 default 적용\n *\n * @example\n * // API Route에서 query params 파싱\n * export async function GET(req: NextRequest) {\n *   const filter = parseSearchParams(req.nextUrl.searchParams);\n *   // filter.keyword, filter.page, filter.pageSize ...\n * }\n */\n\nimport { z } from \"zod\";\n\nexport const baseFilterSchema = z.object({\n    /** 키워드 검색 타입 (예: \"name\" | \"email\") */\n    keywordType: z.string().default(\"\"),\n    /** 키워드 검색어 */\n    keyword: z.string().default(\"\"),\n    /** 날짜 필터 타입 (예: \"createdAt\" | \"updatedAt\") */\n    dateType: z.string().default(\"\"),\n    /** 시작일 (YYYY-MM-DD) */\n    startDate: z.string().default(\"\"),\n    /** 종료일 (YYYY-MM-DD) */\n    endDate: z.string().default(\"\"),\n    /** 자유 형식 옵션 필터 (프로젝트별로 원하는 키/값을 자유롭게 담아 쓴다) */\n    options: z.record(z.string(), z.any()).default({}),\n    /**\n     * 현재 페이지 (1부터 시작). 안 보내면 undefined — 페이지네이션 없이 전체 조회하는\n     * 케이스(옵션 목록 등)와 구분하기 위해 기본값을 강제하지 않는다.\n     * 페이지네이션이 필요한 API에서는 호출부에서 data.page ?? 1 로 기본값을 직접 적용한다.\n     */\n    page: z.coerce.number().int().positive().optional(),\n    /** 페이지당 행 수. 안 보내면 undefined (위 page와 동일한 이유) */\n    pageSize: z.coerce.number().int().positive().optional(),\n});\n\nexport type BaseFilter = z.infer<typeof baseFilterSchema>;\n\n/** 초기값 — useState 초기화에 사용 */\nexport const baseFilterDefaults: BaseFilter = baseFilterSchema.parse({});\n\n/**\n * URLSearchParams → BaseFilter 파싱\n * API Route에서 query params를 안전하게 파싱할 때 사용한다.\n */\nexport function parseSearchParams(searchParams: URLSearchParams): BaseFilter {\n    return baseFilterSchema.parse(Object.fromEntries(searchParams));\n}\n\n/**\n * 날짜 범위 유효성 검사 refine\n * baseFilterSchema.extend({...}).superRefine(dateRangeRefine) 형태로 사용한다.\n *\n * @example\n * const memberFilterSchema = baseFilterSchema\n *   .extend({ role: z.string().default(\"\") })\n *   .superRefine(dateRangeRefine);\n */\nexport function dateRangeRefine(\n    data: { startDate?: string; endDate?: string },\n    ctx: z.RefinementCtx\n) {\n    if (data.startDate && data.endDate && data.startDate > data.endDate) {\n        ctx.addIssue({\n            code: z.ZodIssueCode.custom,\n            message: \"시작일이 종료일보다 클 수 없습니다.\",\n            path: [\"endDate\"],\n        });\n    }\n}\n","/**\n * baseQuerySchema — 목록 API 공통 쿼리 스키마 (필터 + 정렬 + 페이지네이션)\n *\n * baseFilterSchema에 sortKey / sortType을 추가한 버전.\n * antdQuerySchema() 대체용으로 설계되었다.\n *\n * @example\n * const schema = baseQuerySchema.extend({\n *   deliveryKey: z.coerce.number().int().positive().optional(),\n * });\n * const data = parseRequestParams(req, schema);\n * // data.keyword, data.page, data.sortKey, data.sortType ...\n */\n\nimport { z } from \"zod\";\nimport { baseFilterSchema } from \"./base-filter\";\n\nexport const baseQuerySchema = baseFilterSchema.extend({\n    /** 정렬 컬럼 키 (프로젝트에서 sortColumns 맵으로 실제 컬럼명에 매핑) */\n    sortKey: z.string().default(\"\"),\n    /** 정렬 방향 */\n    sortType: z.enum([\"ASC\", \"DESC\", \"asc\", \"desc\"]).optional(),\n});\n\nexport type BaseQuery = z.infer<typeof baseQuerySchema>;\n","/**\n * parseRequestParams — NextRequest의 searchParams를 Zod 스키마로 파싱\n *\n * - URLSearchParams → plain object 변환\n * - bracket 표기법(\"options[progress]\")을 그대로 Zod 키로 사용 가능\n * - 유효성 실패 시 status 400 에러를 throw (apiWrapper의 errorHandler가 처리)\n *\n * @example\n * const schema = baseQuerySchema.extend({\n *   \"options[progress]\": z.string().trim().optional(),\n * }).transform((d) => ({\n *   ...d,\n *   progress: d[\"options[progress]\"],\n * }));\n *\n * const data = parseRequestParams(req, schema);\n * // data.keyword, data.progress, data.page ...\n */\n\nimport { type NextRequest } from \"next/server\";\nimport { z } from \"zod\";\n\nexport function parseRequestParams<T extends z.ZodTypeAny>(\n    req: NextRequest,\n    schema: T\n): z.infer<T> {\n    const { searchParams } = new URL(req.url);\n    const raw = Object.fromEntries(searchParams);\n\n    const result = schema.safeParse(raw);\n    if (!result.success) {\n        const first = result.error.issues[0];\n        const message = first\n            ? `${first.path.join(\".\") || \"파라미터\"}: ${first.message}`\n            : \"유효하지 않은 요청 파라미터입니다.\";\n\n        // errorHandler의 isAppError 조건(code + status)을 만족하도록 설정\n        const err = new Error(message) as Error & { status: number; code: string };\n        err.status = 400;\n        err.code = \"BAD_REQUEST\";\n        throw err;\n    }\n\n    return result.data;\n}\n","/**\n * 문자열 / 문자열배열 / undefined 를 trim된 비어있지 않은 문자열 배열로 정규화한다.\n * \"a,b,c\" 처럼 콤마로 구분된 값이나, [\"a,b\", \"c\"] 처럼 배열 안에 콤마가 섞인 값도 모두 분리한다.\n */\nexport function toStringArray(value: unknown): string[] | undefined {\n    if (value == null || value === \"\") return undefined;\n    const raw = Array.isArray(value) ? value : [value];\n    const cleaned = raw\n        .flatMap((v) => String(v).split(\",\"))\n        .map((v) => v.trim())\n        .filter(Boolean);\n    return cleaned.length > 0 ? cleaned : undefined;\n}\n","/**\n * 내부 전용 zod 스키마 introspection 유틸.\n * extractSearchParams / buildFilterSearchParams 양쪽에서 공유한다\n * (읽기/쓰기가 같은 \"options 화이트리스트 + 타입\" 판단 기준을 써야 포맷이 안 벌어진다).\n */\nimport { z } from \"zod\";\n\n/**\n * ZodDefault/ZodOptional/ZodNullable 래핑만 벗겨서 안의 스키마를 꺼낸다.\n * ZodArray도 자체적으로 .unwrap()(원소 타입 반환)을 갖고 있어서, 벗길 대상을\n * Default/Optional/Nullable로 명시적으로 한정한다 (배열까지 벗겨지는 것을 방지).\n */\nexport function unwrap(schema: unknown): unknown {\n    let current = schema;\n    while (\n        current instanceof z.ZodDefault ||\n        current instanceof z.ZodOptional ||\n        current instanceof z.ZodNullable\n    ) {\n        current =\n            current instanceof z.ZodDefault\n                ? current.removeDefault()\n                : current.unwrap();\n    }\n    return current;\n}\n\n/** 값 하나를 필드 스키마 타입에 맞게 캐스팅한다. 숫자 계열이면 Number로, 그 외엔 문자열 그대로. */\nexport function castValue(raw: string, valueSchema: unknown): unknown {\n    const base = unwrap(valueSchema);\n    if (base instanceof z.ZodNumber) {\n        const n = Number(raw);\n        return Number.isNaN(n) ? raw : n;\n    }\n    return raw;\n}\n\n/** 스키마의 options 필드(z.object(...))의 shape(키별 스키마)를 읽어온다. */\nexport function getOptionsShape(schema: z.ZodObject<z.ZodRawShape>): z.ZodRawShape {\n    const optionsField = schema.shape.options;\n    if (!optionsField) return {};\n    const unwrapped = unwrap(optionsField);\n    if (!(unwrapped instanceof z.ZodObject)) return {};\n    return unwrapped.shape;\n}\n","/**\n * extractSearchParams — URLSearchParams를 baseFilterSchema.parse()에 바로 넣을 수 있는\n * 순수 객체로 변환한다.\n *\n * \"options[key]\" 형태의 값들은 스키마의 options 필드에 정의된 키만 화이트리스트로 인정해서\n * 모아준다. 화이트리스트에 없는 옵션 키는 무시된다. 나머지 필드는 Object.fromEntries와\n * 동일하게 그대로 펼친다.\n *\n * 각 키를 스키마에 적은 타입 그대로 뽑아준다 — 스키마를 두 번 쓸 필요가 없다.\n * - z.array(...) 로 선언 → string[] / number[]\n * - z.string() 등 배열이 아닌 스키마로 선언 → 단일 값\n * - 원소(또는 값) 타입이 z.number() 계열이면 문자열을 숫자로 캐스팅한다.\n *\n * 이 스키마를 buildFilterSearchParams(schema, data)로 다시 URLSearchParams로 만들면\n * 여기서 파싱한 것과 항상 같은 포맷으로 왕복된다 (브라우저 주소창 ↔ SSR ↔ API 요청 통일).\n *\n * @example\n * const orderFilterSchema = baseFilterSchema.extend({\n *   options: z.object({\n *     type: z.array(z.string()).default([]),   // 다중 선택 → string[]\n *     categoryId: z.number().array().default([]), // 다중 선택(숫자) → number[]\n *     status: z.string().optional(),           // 단일 값 → string\n *     page: z.number().optional(),              // 단일 값(숫자) → number\n *   }).default({}),\n * });\n *\n * const { searchParams } = new URL(req.url);\n * const data = orderFilterSchema.parse(\n *   extractSearchParams(orderFilterSchema, searchParams)\n * );\n * // data.options.type       → string[]\n * // data.options.categoryId → number[]\n * // data.options.status     → string | undefined\n * // data.options.page       → number | undefined\n */\nimport { z } from \"zod\";\nimport { toStringArray } from \"./to-string-array\";\nimport { castValue, getOptionsShape, unwrap } from \"./zod-schema-utils\";\n\nexport function extractSearchParams(\n    schema: z.ZodObject<z.ZodRawShape>,\n    searchParams: URLSearchParams\n): Record<string, unknown> {\n    const optionsShape = getOptionsShape(schema);\n\n    const input: Record<string, unknown> = {};\n    for (const [key, value] of searchParams) {\n        if (/^options\\[.+\\]$/.test(key)) continue;\n        input[key] = value;\n    }\n\n    const options: Record<string, unknown> = {};\n    for (const key of Object.keys(optionsShape)) {\n        const bracketKey = `options[${key}]`;\n        const rawValues: string[] = [];\n        for (const [k, v] of searchParams) {\n            // \"options[key]=a\" (반복/콤마) 와 axios 기본 직렬화 \"options[key][0]=a\" 둘 다 받는다.\n            if (k === bracketKey || k.startsWith(`${bracketKey}[`)) {\n                rawValues.push(v);\n            }\n        }\n\n        const fieldSchema = unwrap(optionsShape[key]);\n\n        if (fieldSchema instanceof z.ZodArray) {\n            const cleaned = toStringArray(rawValues) ?? [];\n            options[key] = cleaned.map((v) => castValue(v, fieldSchema.element));\n        } else {\n            const last = rawValues[rawValues.length - 1];\n            options[key] = last === undefined ? undefined : castValue(last, fieldSchema);\n        }\n    }\n    input.options = options;\n\n    return input;\n}\n","/**\n * buildFilterSearchParams — extractSearchParams()의 반대 방향.\n * 파싱된(또는 그와 같은 모양의) 데이터 객체를 URLSearchParams로 직렬화한다.\n *\n * options 필드는 항상 \"options[key]=value\" 브래킷 표기로 쓴다 (extractSearchParams가\n * 읽는 포맷과 동일). 배열은 값마다 반복 append, 스칼라는 set. 빈 값(undefined/null/\"\"/[])은\n * URL을 깨끗하게 유지하기 위해 생략한다.\n *\n * 브라우저 주소창에 필터 상태를 동기화할 때(usePageSearchState 등) 이 함수로 만든\n * URLSearchParams를 그대로 쓰면, 나중에 그 URL을 extractSearchParams로 다시 파싱했을 때\n * 항상 원래 값으로 왕복된다. (SSR에서 같은 URL을 그대로 파싱해도 동일한 결과)\n *\n * @example\n * const data = orderFilterSchema.parse({ keyword: \"hi\", options: { type: [\"A\", \"B\"] } });\n * const sp = buildFilterSearchParams(orderFilterSchema, data);\n * // sp.toString() === \"keyword=hi&options%5Btype%5D=A&options%5Btype%5D=B\"\n *\n * const restored = orderFilterSchema.parse(extractSearchParams(orderFilterSchema, sp));\n * // restored.options.type → [\"A\", \"B\"]  (왕복 성공)\n */\nimport { z } from \"zod\";\nimport { getOptionsShape } from \"./zod-schema-utils\";\n\nfunction isEmpty(value: unknown): boolean {\n    return (\n        value === undefined ||\n        value === null ||\n        value === \"\" ||\n        (Array.isArray(value) && value.length === 0)\n    );\n}\n\nexport function buildFilterSearchParams(\n    schema: z.ZodObject<z.ZodRawShape>,\n    data: Record<string, unknown>\n): URLSearchParams {\n    const optionsShape = getOptionsShape(schema);\n    const sp = new URLSearchParams();\n\n    for (const [key, value] of Object.entries(data)) {\n        if (key === \"options\" || isEmpty(value)) continue;\n        sp.set(key, String(value));\n    }\n\n    const options = (data.options ?? {}) as Record<string, unknown>;\n    for (const key of Object.keys(optionsShape)) {\n        const value = options[key];\n        if (isEmpty(value)) continue;\n\n        const bracketKey = `options[${key}]`;\n        if (Array.isArray(value)) {\n            value.forEach((v) => sp.append(bracketKey, String(v)));\n        } else {\n            sp.set(bracketKey, String(value));\n        }\n    }\n\n    return sp;\n}\n"]}